Merge branch 'main' of upstream into feat/add-css-color-markers-project

This commit is contained in:
Shaun Hamilton
2021-12-20 18:55:22 +00:00
1181 changed files with 6922 additions and 2705 deletions

View File

@@ -62,6 +62,7 @@ jobs:
runs-on: ubuntu-20.04
needs: build-client
strategy:
fail-fast: false
matrix:
browsers: [chrome, firefox, electron]
node-version: [16.x]

View File

@@ -15,6 +15,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
node-version: [16.x]
@@ -47,6 +48,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
node-version: [16.x]

View File

@@ -10,6 +10,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
node-version: [16.x]
@@ -39,6 +40,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
node-version: [16.x]
@@ -70,6 +72,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
node-version: [16.x]
@@ -103,6 +106,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
node-version: [16.x]
locale: [chinese, espanol]

View File

@@ -55,7 +55,7 @@ function destroyAll(id, Model) {
return Observable.fromNodeCallback(Model.destroyAll, Model)({ userId: id });
}
function ensureLowerCaseString(maybeString) {
export function ensureLowerCaseString(maybeString) {
return (maybeString && maybeString.toLowerCase()) || '';
}

View File

@@ -20,6 +20,7 @@ import { reportError } from '../middlewares/sentry-error-handler.js';
import { getChallenges } from '../utils/get-curriculum';
import { ifNoUser401 } from '../utils/middleware';
import { observeQuery } from '../utils/rx';
import { ensureLowerCaseString } from '../../common/models/user';
const {
legacyFrontEndChallengeId,
@@ -523,7 +524,7 @@ function createVerifyCanClaim(certTypeIds, app) {
let certType = superBlockCertTypeMap[superBlock];
log(certType);
return findUserByUsername$(username, {
return findUserByUsername$(ensureLowerCaseString(username), {
isFrontEndCert: true,
isBackEndCert: true,
isFullStackCert: true,
@@ -544,15 +545,36 @@ function createVerifyCanClaim(certTypeIds, app) {
isHonest: true,
completedChallenges: true
}).subscribe(user => {
if (!user) {
return res.status(404).json({
message: {
type: 'info',
message: 'flash.username-not-found',
variables: { username }
}
});
}
if (!certTypeIds[certType]) {
return res.status(404).json({
message: {
type: 'info',
// TODO: create a specific 'flash.cert-not-found' message
message: 'flash.could-not-find'
}
});
}
return Observable.of(certTypeIds[certType])
.flatMap(challenge => {
const certName = certTypeTitleMap[certType];
const { tests = [] } = challenge;
const { isHonest, completedChallenges } = user;
const isProjectsCompleted = canClaim(tests, completedChallenges);
let result = 'incomplete-requirements';
let status = false;
const { isHonest, completedChallenges } = user;
const isProjectsCompleted = canClaim(tests, completedChallenges);
if (isHonest && isProjectsCompleted) {
status = true;
result = 'requirements-met';

View File

@@ -20,13 +20,6 @@ const createByIdentityMap = {
exports.onCreateNode = function onCreateNode({ node, actions, getNode }) {
const { createNodeField } = actions;
if (node.internal.type === 'ChallengeNode') {
const { tests = [], block, dashedName, superBlock } = node;
const slug = `/learn/${superBlock}/${block}/${dashedName}`;
createNodeField({ node, name: 'slug', value: slug });
createNodeField({ node, name: 'blockName', value: blockNameify(block) });
createNodeField({ node, name: 'tests', value: tests });
}
if (node.internal.type === 'MarkdownRemark') {
const slug = createFilePath({ node, getNode });
@@ -71,37 +64,47 @@ exports.createPages = function createPages({ graphql, actions, reporter }) {
graphql(`
{
allChallengeNode(
sort: { fields: [superOrder, order, challengeOrder] }
sort: {
fields: [
challenge___superOrder
challenge___order
challenge___challengeOrder
]
}
) {
edges {
node {
block
challengeType
fields {
slug
challenge {
block
certification
challengeType
fields {
slug
}
hasEditableBoundaries
id
order
required {
link
src
}
challengeOrder
challengeFiles {
name
ext
contents
head
tail
}
solutions {
contents
ext
}
superBlock
superOrder
template
usesMultifileEditor
}
id
order
required {
link
src
}
challengeOrder
challengeFiles {
name
ext
contents
head
tail
}
solutions {
contents
ext
}
superBlock
superOrder
template
usesMultifileEditor
}
}
}
@@ -114,6 +117,7 @@ exports.createPages = function createPages({ graphql, actions, reporter }) {
component
}
frontmatter {
certification
block
superBlock
title
@@ -137,12 +141,22 @@ exports.createPages = function createPages({ graphql, actions, reporter }) {
);
const blocks = uniq(
result.data.allChallengeNode.edges.map(({ node: { block } }) => block)
result.data.allChallengeNode.edges.map(
({
node: {
challenge: { block }
}
}) => block
)
).map(block => blockNameify(block));
const superBlocks = uniq(
result.data.allChallengeNode.edges.map(
({ node: { superBlock } }) => superBlock
({
node: {
challenge: { superBlock }
}
}) => superBlock
)
);
@@ -171,6 +185,7 @@ exports.createPages = function createPages({ graphql, actions, reporter }) {
const pageBuilder = createByIdentityMap[nodeIdentity](createPage);
pageBuilder(edge);
} catch (e) {
console.log(e);
console.log(`
ident: ${nodeIdentity} does not belong to a function
@@ -256,6 +271,9 @@ exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions;
const typeDefs = `
type ChallengeNode implements Node {
challenge: Challenge
}
type Challenge {
challengeFiles: [FileContents]
notes: String
url: String

View File

@@ -32,7 +32,6 @@
"profile": "個人資料",
"news": "專欄",
"donate": "捐款",
"support-our-nonprofit": "Support our non-profit",
"update-settings": "更新我的賬號設置",
"sign-me-out": "退出登錄 freeCodeCamp",
"flag-user": "標記該用戶的賬號爲濫用",
@@ -217,8 +216,7 @@
"tax-exempt-status": "freeCodeCamp 是捐助者支持的 501(c)(3) 條款下具有免稅資格的非營利性組織稅號82-0779546)。",
"mission-statement": "我們的使命:幫助人們免費學習編程。我們通過創建成千上萬的視頻、文章和交互式編程課程——所有內容向公衆免費開放——來實現這一目標。學員在世界各地自發成立數千個 freeCodeCamp 學習小組。",
"donation-initiatives": "所有給 freeCodeCamp 的捐款都將用於我們的教育項目,購買服務器和其他服務,以及聘用員工。",
"donate-text": "你可以",
"donate-link": "點擊此處免稅捐款",
"donate-text": "You can <1>make a tax-deductible donation here</1>.",
"trending-guides": "精選文章",
"our-nonprofit": "關於我們",
"links": {
@@ -287,11 +285,16 @@
"info": "信息",
"code": "編程",
"tests": "測試",
"restart": "Restart",
"restart-step": "Restart Step",
"console": "Console",
"notes": "Notes",
"preview": "預覽"
},
"help-translate": "我們仍然在翻譯以下證書。",
"help-translate-link": "幫助我們翻譯。",
"season-greetings": "Season's Greetings to you and your family.",
"season-greetings-fcc": "Season's Greetings from the freeCodeCamp community 🎉",
"if-getting-value": "If you're getting a lot out of freeCodeCamp, now is a great time to donate to support our nonprofit's mission.",
"project-preview-title": "下面是你將構建的項目的預覽"
},
@@ -459,7 +462,7 @@
"username-not-found": "我們未找到用戶名爲 \"{{username}}\" 的用戶",
"add-name": "這個用戶需要在賬號中添加名字,以便其他人查看該用戶的認證。",
"not-eligible": "這個用戶目前不符合 freeCodeCamp.org 認證的條件。",
"profile-private": "{{username}} 已將其作品集設置爲僅自己可見。用戶需要將作品集設置爲公開,其他人才能查看該用戶的認證。",
"profile-private": "{{username}} has chosen to make their profile private. They will need to make their profile public in order for others to be able to view their certification.",
"certs-private": "{{username}} 已將其作品集設置爲僅自己可見。用戶需要將認證設置爲公開,其他人才能查看該用戶的認證。",
"not-honest": "{{username}} 未接受我們的《學術誠信條例》。",
"user-not-certified": "用戶 {{username}} 似乎未獲得 {{cert}} 認證",

View File

@@ -32,7 +32,6 @@
"profile": "个人资料",
"news": "专栏",
"donate": "捐款",
"support-our-nonprofit": "Support our non-profit",
"update-settings": "更新我的账号设置",
"sign-me-out": "退出登录 freeCodeCamp",
"flag-user": "标记该用户的账号为滥用",
@@ -217,8 +216,7 @@
"tax-exempt-status": "freeCodeCamp 是捐助者支持的 501(c)(3) 条款下具有免税资格的非营利性组织税号82-0779546)。",
"mission-statement": "我们的使命:帮助人们免费学习编程。我们通过创建成千上万的视频、文章和交互式编程课程——所有内容向公众免费开放——来实现这一目标。学员在世界各地自发成立数千个 freeCodeCamp 学习小组。",
"donation-initiatives": "所有给 freeCodeCamp 的捐款都将用于我们的教育项目,购买服务器和其他服务,以及聘用员工。",
"donate-text": "你可以",
"donate-link": "点击此处免税捐款",
"donate-text": "You can <1>make a tax-deductible donation here</1>.",
"trending-guides": "精选文章",
"our-nonprofit": "关于我们",
"links": {
@@ -287,11 +285,16 @@
"info": "信息",
"code": "编程",
"tests": "测试",
"restart": "Restart",
"restart-step": "Restart Step",
"console": "Console",
"notes": "Notes",
"preview": "预览"
},
"help-translate": "我们仍然在翻译以下证书。",
"help-translate-link": "帮助我们翻译。",
"season-greetings": "Season's Greetings to you and your family.",
"season-greetings-fcc": "Season's Greetings from the freeCodeCamp community 🎉",
"if-getting-value": "If you're getting a lot out of freeCodeCamp, now is a great time to donate to support our nonprofit's mission.",
"project-preview-title": "下面是你将构建的项目的预览"
},
@@ -459,7 +462,7 @@
"username-not-found": "我们未找到用户名为 \"{{username}}\" 的用户",
"add-name": "这个用户需要在账号中添加名字,以便其他人查看该用户的认证。",
"not-eligible": "这个用户目前不符合 freeCodeCamp.org 认证的条件。",
"profile-private": "{{username}} 已将其作品集设置为仅自己可见。用户需要将作品集设置为公开,其他人才能查看该用户的认证。",
"profile-private": "{{username}} has chosen to make their profile private. They will need to make their profile public in order for others to be able to view their certification.",
"certs-private": "{{username}} 已将其作品集设置为仅自己可见。用户需要将认证设置为公开,其他人才能查看该用户的认证。",
"not-honest": "{{username}} 未接受我们的《学术诚信条例》。",
"user-not-certified": "用户 {{username}} 似乎未获得 {{cert}} 认证",

View File

@@ -63,94 +63,127 @@
"Time to put your newly learnt skills to work. By working on these projects, you will get a chance to apply all of the skills, principles, and concepts you have learned so far: HTML, CSS, Visual Design, Accessibility, and more.",
"Complete the five web programming projects below to earn your Responsive Web Design certification."
]
}
}
},
"responsive-web-design-22": {
"title": "Responsive Web Design",
"intro": [
"In this Responsive Web Design Certification, you'll learn the languages that developers use to build webpages: HTML (Hypertext Markup Language) for content, and CSS (Cascading Style Sheets) for design.",
"First, you'll build a cat photo app to learn the basics of HTML and CSS. Later, you'll learn modern techniques like CSS variables by building a penguin, and best practices for accessibility by building a web form.",
"Finally, you'll learn how to make webpages that respond to different screen sizes by building a Twitter card with Flexbox, and a complex blog layout with CSS Grid."
],
"note": "Note: Some browser extensions, such as ad-blockers and dark mode extensions can interfere with the tests. If you face issues, we recommend disabling extensions that modify the content or layout of pages, while taking the course.",
"blocks": {
"build-a-tribute-page-project": {
"title": "Build a Tribute Page Project",
"intro": ["placeholder", "holder of place"]
},
"learn-html-by-building-a-cat-photo-app": {
"title": "Learn HTML by Building a Cat Photo App",
"intro": [
"",
""
]
"build-a-personal-portfolio-webpage-project": {
"title": "Build a Personal Portfolio Webpage Project",
"intro": ["placeholder", "holder of place"]
},
"learn-basic-css-by-building-a-cafe-menu": {
"title": "Learn Basic CSS by Building a Cafe Menu",
"intro": [
"",
""
]
"build-a-product-landing-page-project": {
"title": "Build a Product Landing Page Project",
"intro": ["placeholder", "holder of place"]
},
"learn-the-css-box-model-by-building-a-rothko-painting": {
"title": "Learn the CSS Box Model by Building a Rothko Painting",
"intro": [
"Mark Rothko was known for his abstract style of painting. Rothko paintings commonly depicted rectangular regions of color in varying sizes.",
"In this lesson, you will be using CSS to create your own painting in Rothko's style. You will use many of the skills you have already been practicing, as well as new CSS tool such as blur and transform."
]
"build-a-survey-form-project": {
"title": "Build a Survey Form Project",
"intro": ["placeholder", "holder of place"]
},
"learn-css-variables-by-building-a-city-skyline": {
"title": "Learn CSS Variables by Building a City Skyline",
"intro": [
"",
""
]
"build-a-technical-documentation-page-project": {
"title": "Build a Technical Documentation Page Project",
"intro": ["placeholder", "holder of place"]
},
"learn-html-forms-by-building-a-registration-form": {
"title": "Learn HTML Forms by Building a Registration Form",
"intro": [
"",
""
]
},
"learn-accessibility-by-building-a-quiz": {
"title": "Learn Accessibility by Building a Quiz",
"intro": [
"",
""
]
},
"learn-intermediate-css-by-building-a-picasso-painting": {
"title": "Learn Intermediate CSS by Building a Picasso Painting",
"intro": [
"Pablo Picasso was known for his Cubism style of painting, a style recognized by representations of objects broken down and reassembled from multiple perspectives. Picasso's paintings are often highly abstract and thought provoking.",
"In this course, you will use CSS to create your own painting in the style of Picasso. You will learn about FontAwesome SVG icons, CSS positioning, and reinforce the skills you have already been learning."
]
},
"learn-responsive-web-design-by-building-a-piano": {
"title": "Learn Responsive Web Design by Building a Piano",
"intro": [
"",
""
]
},
"learn-css-flexbox-by-building-a-photo-gallery": {
"title": "Learn CSS Flexbox by Building a Photo Gallery",
"intro": [
"",
""
]
},
"learn-css-grid-by-building-a-magazine": {
"title": "Learn CSS Grid by Building a Magazine",
"intro": [
"",
""
]
},
"learn-typography-by-building-a-nutrition-label": {
"title": "Learn Typography by Building a Nutrition Label",
"intro": [
"",
""
]
},
"learn-css-transforms-by-building-a-penguin": { "title": "Learn CSS Transforms by Building a Penguin", "intro": ["", ""] },
"learn-css-animation-by-building-a-ferris-wheel": { "title": "Learn CSS Animation by Building a Ferris Wheel", "intro": ["", ""] },
"learn-more-about-css-pseudo-selectors-by-building-a-balance-sheet": { "title": "Learn More About CSS Pseudo Selectors By Building A Balance Sheet", "intro": ["", ""] },
"learn-css-colors-by-building-a-color-markers-set": {
"title": "Learn CSS Colors by Building a Color Markers Set",
"intro": [
"",
""
]
}
},
"learn-html-by-building-a-cat-photo-app": {
"title": "Learn HTML by Building a Cat Photo App",
"intro": [
"HTML tags give a webpage its structure. You can use HTML tags to add photos, buttons, and other elements to your webpage.",
"In this course, you'll learn the most common HTML tags by building your own cat photo app."
]
},
"learn-basic-css-by-building-a-cafe-menu": {
"title": "Learn Basic CSS by Building a Cafe Menu",
"intro": [
"CSS tells the browser how to display your webpage. You can use CSS to set the color, font, size, and other aspects of HTML elements.",
"In this course, you'll learn CSS by designing a menu page for a cafe webpage."
]
},
"learn-the-css-box-model-by-building-a-rothko-painting": {
"title": "Learn the CSS Box Model by Building a Rothko Painting",
"intro": [
"Every HTML element is its own box with its own spacing and a border. This is called the Box Model.",
"In this course, you'll use CSS and the Box Model to create your own Rothko-style rectangular art pieces."
]
},
"learn-css-variables-by-building-a-city-skyline": {
"title": "Learn CSS Variables by Building a City Skyline",
"intro": [
"CSS variables help you organize your styles and reuse them.",
"In this course, you'll build a city skyline. You'll learn how to configure CSS variables so you can reuse them whenever you want."
]
},
"learn-html-forms-by-building-a-registration-form": {
"title": "Learn HTML Forms by Building a Registration Form",
"intro": [
"You can use HTML forms to collect information from people who visit your webpage.",
"In this course, you'll learn HTML forms by building a signup page. You'll learn how to control what types of data people can type into your form, and some new CSS tools for styling your page."
]
},
"learn-accessibility-by-building-a-quiz": {
"title": "Learn Accessibility by Building a Quiz",
"intro": [
"Accessibility is making your webpage easy for all people to use even people with disabilities.",
"In this course, you'll build a quiz webpage. You'll learn accessibility tools such as keyboard shortcuts, ARIA attributes, and design best practices."
]
},
"learn-intermediate-css-by-building-a-picasso-painting": {
"title": "Learn Intermediate CSS by Building a Picasso Painting",
"intro": [
"In this course, you'll use learn some intermediate CSS techniques by coding your own Picasso painting webpage. You'll learn about SVG icons, CSS positioning, and review other CSS skills you've learned."
]
},
"learn-responsive-web-design-by-building-a-piano": {
"title": "Learn Responsive Web Design by Building a Piano",
"intro": [
"Responsive Design tells your webpage how it should look on different-sized screens.",
"In this course, you'll use CSS and Responsive Design to code a piano. You'll also learn more about media queries and pseudo selectors."
]
},
"learn-css-flexbox-by-building-a-photo-gallery": {
"title": "Learn CSS Flexbox by Building a Photo Gallery",
"intro": [
"Flexbox helps you design your webpage so that it looks good on any screen size.",
"In this course, you'll use Flexbox to build a responsive photo gallery webpage."
]
},
"learn-css-grid-by-building-a-magazine": {
"title": "Learn CSS Grid by Building a Magazine",
"intro": [
"CSS Grid gives you control over the rows and columns of your webpage design.",
"In this course, you'll build a magazine article. You'll learn how to use CSS Grid, including concepts like grid rows and grid columns."
]
},
"learn-typography-by-building-a-nutrition-label": {
"title": "Learn Typography by Building a Nutrition Label",
"intro": [
"Typography is the art of styling your text to be easily readable and suit its purpose.",
"In this course, you'll use typography to build a nutrition label webpage. You'll learn how to style text, adjust line height, and position your text using CSS."
]
},
"learn-css-transforms-by-building-a-penguin": { "title": "Learn CSS Transforms by Building a Penguin", "intro": ["", ""] },
"learn-css-animation-by-building-a-ferris-wheel": { "title": "Learn CSS Animation by Building a Ferris Wheel", "intro": [
"You can use CSS animation to draw attention to specific sections of your webpage and make it more engaging.",
"In this course, you'll build a Ferris wheel. You'll learn how to use CSS to animate elements, transform them, and adjust their speed."
] },
"learn-more-about-css-pseudo-selectors-by-building-a-balance-sheet": { "title": "Learn More About CSS Pseudo Selectors By Building A Balance Sheet", "intro": ["", ""] }
}
},
"javascript-algorithms-and-data-structures": {
@@ -622,6 +655,10 @@
"courses": "Courses",
"steps": "Steps",
"expand": "Expand",
"collapse": "Collapse"
"collapse": "Collapse",
"expand": "Expand courses",
"collapse": "Collapse courses",
"legacy-header": "Legacy Courses",
"legacy-desc": "These courses are no longer part of the certification path, but are still available for you to further your learning."
}
}

View File

@@ -32,7 +32,6 @@
"profile": "Profile",
"news": "News",
"donate": "Donate",
"support-our-nonprofit": "Support our non-profit",
"update-settings": "Update my account settings",
"sign-me-out": "Sign me out of freeCodeCamp",
"flag-user": "Flag This User's Account for Abuse",
@@ -217,8 +216,7 @@
"tax-exempt-status": "freeCodeCamp is a donor-supported tax-exempt 501(c)(3) nonprofit organization (United States Federal Tax Identification Number: 82-0779546)",
"mission-statement": "Our mission: to help people learn to code for free. We accomplish this by creating thousands of videos, articles, and interactive coding lessons - all freely available to the public. We also have thousands of freeCodeCamp study groups around the world.",
"donation-initiatives": "Donations to freeCodeCamp go toward our education initiatives, and help pay for servers, services, and staff.",
"donate-text": "You can",
"donate-link": "make a tax-deductible donation here",
"donate-text": "You can <1>make a tax-deductible donation here</1>.",
"trending-guides": "Trending Guides",
"our-nonprofit": "Our Nonprofit",
"links": {
@@ -464,7 +462,7 @@
"username-not-found": "We could not find a user with the username \"{{username}}\"",
"add-name": "This user needs to add their name to their account in order for others to be able to view their certification.",
"not-eligible": "This user is not eligible for freeCodeCamp.org certifications at this time.",
"profile-private": "{{username}} has chosen to make their portfolio private. They will need to make their portfolio public in order for others to be able to view their certification.",
"profile-private": "{{username}} has chosen to make their profile private. They will need to make their profile public in order for others to be able to view their certification.",
"certs-private": "{{username}} has chosen to make their certifications private. They will need to make their certifications public in order for others to be able to view them.",
"not-honest": "{{username}} has not yet agreed to our Academic Honesty Pledge.",
"user-not-certified": "It looks like user {{username}} is not {{cert}} certified",

View File

@@ -32,7 +32,6 @@
"profile": "Perfil",
"news": "Noticias",
"donate": "Donar",
"support-our-nonprofit": "Apoya a nuestra organización sin fines de lucro",
"update-settings": "Actualizar los ajustes de mi cuenta",
"sign-me-out": "Cerrar sesión en freeCodeCamp",
"flag-user": "Marcar la cuenta de este usuario por abuso",
@@ -217,8 +216,7 @@
"tax-exempt-status": "freeCodeCamp es una organización sin fines de lucro exenta de impuestos 501(c)(3) respaldada por donantes (Número de identificación fiscal federal de los Estados Unidos: 82-0779546)",
"mission-statement": "Nuestra misión: ayudar a las personas a aprender a programar de forma gratuita. Logramos esto mediante la creación de miles de videos, artículos y lecciones de programación interactivas, todos disponibles gratuitamente para el público. También tenemos miles de grupos de estudio de FreeCodeCamp en todo el mundo.",
"donation-initiatives": "Las donaciones a freeCodeCamp se destinan a nuestras iniciativas educativas y ayudan a pagar los servidores, los servicios y el personal.",
"donate-text": "Tú puedes",
"donate-link": "hacer una donación deducible de impuestos aquí",
"donate-text": "You can <1>make a tax-deductible donation here</1>.",
"trending-guides": "Guías de tendencias",
"our-nonprofit": "Nuestra organización sin fines de lucro",
"links": {
@@ -287,11 +285,16 @@
"info": "Info",
"code": "Código",
"tests": "Pruebas",
"restart": "Restart",
"restart-step": "Restart Step",
"console": "Console",
"notes": "Notes",
"preview": "Vista"
},
"help-translate": "Todavía estamos traduciendo las siguientes certificaciones.",
"help-translate-link": "Ayúdanos a traducir.",
"season-greetings": "Deseamos felices festividades a ti y a tu familia.",
"season-greetings-fcc": "Season's Greetings from the freeCodeCamp community 🎉",
"if-getting-value": "Si estás obteniendo mucho de freeCodeCamp, ahora es un buen momento para donar para apoyar nuestra misión sin fines de lucro.",
"project-preview-title": "Aquí hay una vista previa de lo que construirás"
},
@@ -459,7 +462,7 @@
"username-not-found": "No pudimos encontrar un usuario con el nombre de usuario \"{{username}}\"",
"add-name": "Este usuario debe agregar su nombre a su cuenta para que otros puedan ver su certificación.",
"not-eligible": "Este usuario no es elegible para las certificaciones de freeCodeCamp.org en este momento.",
"profile-private": "{{username}} ha elegido que su portafolio sea privado. Deberá hacer público su portafolio para que otros puedan ver su certificación.",
"profile-private": "{{username}} has chosen to make their profile private. They will need to make their profile public in order for others to be able to view their certification.",
"certs-private": "{{username}} ha elegido que sus certificaciones sean privadas. Deberá hacer públicas sus certificaciones para que otros puedan verlas.",
"not-honest": "{{username}} aún no ha aceptado nuestro Compromiso de Honestidad Académica.",
"user-not-certified": "Parece que el usuario {{username}} no está certificado por {{cert}}",

View File

@@ -32,7 +32,6 @@
"profile": "Profilo",
"news": "Notizie",
"donate": "Dona",
"support-our-nonprofit": "Support our non-profit",
"update-settings": "Aggiorna le impostazioni del mio account",
"sign-me-out": "Esci da freeCodeCamp",
"flag-user": "Segnala l'account di questo utente per abusi",
@@ -217,8 +216,7 @@
"tax-exempt-status": "freeCodeCamp è un'organizzazione senza scopo di lucro (United States Federal Tax Identification Number: 82-0779546)",
"mission-statement": "La nostra missione: aiutare le persone a imparare a programmare gratuitamente. Lo facciamo creando migliaia di video, articoli e lezioni di programmazione interattive - tutti liberamente disponibili al pubblico. Abbiamo anche migliaia di gruppi di studio freeCodeCamp in tutto il mondo.",
"donation-initiatives": "Le donazioni a freeCodeCamp vanno alle nostre iniziative educative e aiutano a pagare i server, i servizi e il personale.",
"donate-text": "Puoi",
"donate-link": "fare una donazione deducibile dalle tasse qui",
"donate-text": "You can <1>make a tax-deductible donation here</1>.",
"trending-guides": "Guide Di Tendenza",
"our-nonprofit": "Il nostro nonprofit",
"links": {
@@ -287,11 +285,16 @@
"info": "Informazioni",
"code": "Codice",
"tests": "Test",
"restart": "Restart",
"restart-step": "Restart Step",
"console": "Console",
"notes": "Notes",
"preview": "Anteprima"
},
"help-translate": "Stiamo ancora traducendo le seguenti certificazioni.",
"help-translate-link": "Aiutaci con le traduzioni...",
"season-greetings": "Season's Greetings to you and your family.",
"season-greetings-fcc": "Season's Greetings from the freeCodeCamp community 🎉",
"if-getting-value": "If you're getting a lot out of freeCodeCamp, now is a great time to donate to support our nonprofit's mission.",
"project-preview-title": "Here's a preview of what you will build"
},
@@ -459,7 +462,7 @@
"username-not-found": "Non siamo riusciti a trovare un utente con il nome utente \"{{username}}\"",
"add-name": "Questo utente deve aggiungere il proprio nome al proprio account affinché gli altri possano visualizzare la sua certificazione.",
"not-eligible": "Questo utente non è idoneo per le certificazioni freeCodeCamp.org al momento.",
"profile-private": "{{username}} ha scelto di rendere privato il suo portfolio. Dovrà renderlo pubblico affinché altri possano vedere la sua certificazione.",
"profile-private": "{{username}} has chosen to make their profile private. They will need to make their profile public in order for others to be able to view their certification.",
"certs-private": "{{username}} ha scelto di rendere privato il suo portfolio. Dovrà renderlo pubblico affinché altri possano vedere la sua certificazione.",
"not-honest": "{{username}} non ha ancora accettato il nostro Impegno di Onestà Accademica.",
"user-not-certified": "Sembra che l'utente {{username}} non sia certificato {{cert}}",

View File

@@ -32,7 +32,6 @@
"profile": "Perfil",
"news": "Notícias",
"donate": "Doar",
"support-our-nonprofit": "Dê seu apoio à nossa organização sem fins lucrativos",
"update-settings": "Atualizar as configurações da minha conta",
"sign-me-out": "Sair do freeCodeCamp",
"flag-user": "Denunciar a conta deste usuário por abuso",
@@ -217,8 +216,7 @@
"tax-exempt-status": "O freeCodeCamp é uma organização sem fins lucrativos apoiada por doadores e isenta de impostos 501(c)(3) (Número de identificação fiscal federal dos Estados Unidos: 82-0779546)",
"mission-statement": "Nossa missão: ajudar as pessoas a aprender a programar de graça. Nós conseguimos isso através da criação de milhares de vídeos, artigos e aulas interativas de programação tudo disponível gratuitamente para o público. Também temos milhares de grupos de estudo do freeCodeCamp espalhados por todo o mundo.",
"donation-initiatives": "Doações para o freeCodeCamp vão para as nossas iniciativas educacionais e ajudam a pagar por servidores, serviços e equipe.",
"donate-text": "Você pode",
"donate-link": "fazer uma doação dedutível de impostos aqui",
"donate-text": "Você pode <1>fazer uma doação dedutível de imposto aqui</1>.",
"trending-guides": "Guias em alta",
"our-nonprofit": "Nossa organização sem fins lucrativos",
"links": {
@@ -287,11 +285,16 @@
"info": "Informações",
"code": "Código",
"tests": "Testes",
"restart": "Reiniciar",
"restart-step": "Reiniciar etapa",
"console": "Console",
"notes": "Observações",
"preview": "Pré-visualizar"
},
"help-translate": "Ainda estamos traduzindo as certificações a seguir.",
"help-translate-link": "Ajude-nos a traduzir.",
"season-greetings": "Boas festas de fim de ano para você e sua família.",
"season-greetings-fcc": "Boas festas de fim de ano, da comunidade do freeCodeCamp 🎉",
"if-getting-value": "Se você está conseguindo tirar proveito do conteúdo do freeCodeCamp, agora é um ótimo momento para fazer sua doação e apoiar nossa missão sem fins lucrativos.",
"project-preview-title": "Aqui está uma prévia do que você vai criar"
},
@@ -459,7 +462,7 @@
"username-not-found": "Não foi possível encontrar um usuário com o nome de usuário \"{{username}}\"",
"add-name": "Este usuário precisa adicionar seu nome à sua conta para que outros possam visualizar sua certificação.",
"not-eligible": "Este usuário não é elegível para certificações do freeCodeCamp.org no momento.",
"profile-private": "{{username}} escolheu tornar seu portfólio privado. Ele/ela terá que tornar seu portfólio público para que outros possam ver a sua certificação.",
"profile-private": "{{username}} escolheu tornar seu perfil privado. Ele/ela terá que tornar seu perfil público para que outros possam ver a sua certificação.",
"certs-private": "{{username}} escolheu deixar suas certificações privadas. Ele/ela terá que tornar suas certificações públicas para que outros possam vê-las.",
"not-honest": "{{username}} ainda não concordou com nosso compromisso com a honestidade acadêmica.",
"user-not-certified": "Parece que o usuário {{username}} não possui o certificado {{cert}}",

View File

@@ -0,0 +1,28 @@
{
"help-translate-link-url": "https://contribute.freecodecamp.org/#/how-to-translate-files",
"top-contributors": "https://www.freecodecamp.org/news/freecodecamp-top-contributors/",
"footer": {
"about-url": "https://www.freecodecamp.org/news/about/",
"shop-url": "https://www.freecodecamp.org/shop/",
"support-url": "https://www.freecodecamp.org/news/support/",
"sponsors-url": "https://www.freecodecamp.org/news/sponsors/",
"honesty-url": "https://www.freecodecamp.org/news/academic-honesty-policy/",
"coc-url": "https://www.freecodecamp.org/news/code-of-conduct/",
"privacy-url": "https://www.freecodecamp.org/news/privacy-policy/",
"tos-url": "https://www.freecodecamp.org/news/terms-of-service/",
"copyright-url": "https://www.freecodecamp.org/news/copyright-policy/"
},
"donate": {
"other-ways-url": "https://www.freecodecamp.org/news/how-to-donate-to-free-code-camp"
},
"nav": {
"forum": "https://forum.freecodecamp.org/",
"news": "https://freecodecamp.org/news/"
},
"help": {
"HTML-CSS": "HTML-CSS",
"JavaScript": "JavaScript",
"Python": "Python",
"Relational Databases": "Relational Databases"
}
}

View File

@@ -0,0 +1,32 @@
{
"title": "Learn to Code — For Free — Coding Courses for Busy People",
"description": "Learn to Code — For Free",
"social-description": "Learn to Code — For Free",
"keywords": [
"python",
"javascript",
"js",
"git",
"github",
"website",
"web",
"development",
"free",
"code",
"camp",
"course",
"courses",
"html",
"css",
"react",
"redux",
"api",
"front",
"back",
"end",
"learn",
"tutorial",
"programming"
],
"youre-unsubscribed": "You have been unsubscribed"
}

View File

@@ -0,0 +1,819 @@
{
"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!",
"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!",
"You've got the touch!",
"You're on fire!",
"The town is now red!",
"To the nines!",
"To the Batmobile!",
"Pull out all the stops!",
"You're a wizard, Harry!",
"You're an all star!",
"Way to go!",
"Outta sight!",
"You're crushing it!",
"What sorcery is this?",
"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!",
"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!",
"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!",
"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!",
"Standing ovation!",
"Nice one!",
"All right!",
"Hasta la vista, challenge!",
"Terminated.",
"Off the hook!",
"Thundercats, Hooo!",
"Shiver me timbers!",
"Raise the roof!",
"Bingo!",
"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!",
"Pikachu chooses you!",
"I gotta have more cow bell.",
"Gotta go fast!",
"Yipee!",
"Cowabunga!",
"Moon Prism Power!",
"Plus Ultra!"
],
"motivationalQuotes": [
{
"quote": "Whatever you are, be a good one.",
"author": "Abraham Lincoln"
},
{
"quote": "A change in perspective is worth 80 IQ points.",
"author": "Alan Kay"
},
{
"quote": "The best way to predict the future is to invent it.",
"author": "Alan Kay"
},
{
"quote": "The future is not laid out on a track. It is something that we can decide, and to the extent that we do not violate any known laws of the universe, we can probably make it work the way that we want to.",
"author": "Alan Kay"
},
{
"quote": "We can only see a short distance ahead, but we can see plenty there that needs to be done.",
"author": "Alan Turing"
},
{
"quote": "In the depth of winter, I finally learned that within me there lay an invincible summer.",
"author": "Albert Camus"
},
{
"quote": "A person who never made a mistake never tried anything new.",
"author": "Albert Einstein"
},
{
"quote": "Creativity is intelligence having fun.",
"author": "Albert Einstein"
},
{
"quote": "I have no special talents. I am only passionately curious.",
"author": "Albert Einstein"
},
{
"quote": "Life is like riding a bicycle. To keep your balance, you must keep moving.",
"author": "Albert Einstein"
},
{
"quote": "Make everything as simple as possible, but not simpler.",
"author": "Albert Einstein"
},
{
"quote": "Never memorize something that you can look up.",
"author": "Albert Einstein"
},
{
"quote": "Once we accept our limits, we go beyond them.",
"author": "Albert Einstein"
},
{
"quote": "Play is the highest form of research.",
"author": "Albert Einstein"
},
{
"quote": "We cannot solve our problems with the same thinking we used when we created them.",
"author": "Albert Einstein"
},
{
"quote": "Wisdom is not a product of schooling but of the lifelong attempt to acquire it.",
"author": "Albert Einstein"
},
{
"quote": "Your imagination is your preview of life's coming attractions.",
"author": "Albert Einstein"
},
{
"quote": "There is only one corner of the universe you can be certain of improving, and that's your own self.",
"author": "Aldous Huxley"
},
{
"quote": "The most common way people give up their power is by thinking they don't have any.",
"author": "Alice Walker"
},
{
"quote": "Follow your inner moonlight. Don't hide the madness.",
"author": "Allen Ginsberg"
},
{
"quote": "The most difficult thing is the decision to act. The rest is merely tenacity.",
"author": "Amelia Earhart"
},
{
"quote": "Life shrinks or expands in proportion with one's courage.",
"author": "Anaïs Nin"
},
{
"quote": "Weeks of programming can save you hours of planning.",
"author": "Unknown"
},
{
"quote": "Quality is not an act, it is a habit.",
"author": "Aristotle"
},
{
"quote": "Start where you are. Use what you have. Do what you can.",
"author": "Arthur Ashe"
},
{
"quote": "Nothing is impossible, the word itself says \"I'm possible\"!",
"author": "Audrey Hepburn"
},
{
"quote": "Every strike brings me closer to the next home run.",
"author": "Babe Ruth"
},
{
"quote": "By failing to prepare, you are preparing to fail.",
"author": "Benjamin Franklin"
},
{
"quote": "Tell me and I forget. Teach me and I remember. Involve me and I learn.",
"author": "Benjamin Franklin"
},
{
"quote": "Well done is better than well said.",
"author": "Benjamin Franklin"
},
{
"quote": "There are no short cuts to any place worth going.",
"author": "Beverly Sills"
},
{
"quote": "Controlling complexity is the essence of computer programming.",
"author": "Brian Kernighan"
},
{
"quote": "I fear not the man who has practiced 10,000 kicks once, but I fear the man who has practiced one kick 10,000 times.",
"author": "Bruce Lee"
},
{
"quote": "There are far, far better things ahead than any we leave behind.",
"author": "C.S. Lewis"
},
{
"quote": "We are what we believe we are.",
"author": "C.S. Lewis"
},
{
"quote": "With the possible exception of the equator, everything begins somewhere.",
"author": "C.S. Lewis"
},
{
"quote": "You are never too old to set another goal, or to dream a new dream.",
"author": "C.S. Lewis"
},
{
"quote": "Somewhere, something incredible is waiting to be known.",
"author": "Carl Sagan"
},
{
"quote": "If you're not making mistakes, then you're not making decisions.",
"author": "Catherine Cook"
},
{
"quote": "Find what you love and let it kill you.",
"author": "Charles Bukowski"
},
{
"quote": "What matters most is how well you walk through the fire.",
"author": "Charles Bukowski"
},
{
"quote": "It is not the strongest of the species that survive, nor the most intelligent, but the one most responsive to change.",
"author": "Charles Darwin"
},
{
"quote": "Life is 10% what happens to you and 90% how you react to it.",
"author": "Charles R. Swindoll"
},
{
"quote": "You will do foolish things, but do them with enthusiasm.",
"author": "Colette"
},
{
"quote": "It does not matter how slowly you go as long as you do not stop.",
"author": "Confucius"
},
{
"quote": "Real knowledge is to know the extent of one's ignorance.",
"author": "Confucius"
},
{
"quote": "The past cannot be changed. The future is yet in your power.",
"author": "Confucius"
},
{
"quote": "Looking at code you wrote more than two weeks ago is like looking at code you are seeing for the first time.",
"author": "Dan Hurvitz"
},
{
"quote": "Someday is not a day of the week.",
"author": "Denise Brennan-Nelson"
},
{
"quote": "UNIX is simple. It just takes a genius to understand its simplicity.",
"author": "Dennis Ritchie"
},
{
"quote": "Computers are good at following instructions, but not at reading your mind.",
"author": "Donald Knuth"
},
{
"quote": "A good programmer is someone who always looks both ways before crossing a one-way street.",
"author": "Doug Linder"
},
{
"quote": "Tough times never last, but tough people do.",
"author": "Dr. Robert Schuller"
},
{
"quote": "If things start happening, don't worry, don't stew, just go right along and you'll start happening too.",
"author": "Dr. Seuss"
},
{
"quote": "Do not go gentle into that good night. Rage, rage against the dying of the light.",
"author": "Dylan Thomas"
},
{
"quote": "The question of whether computers can think is like the question of whether submarines can swim.",
"author": "E.W. Dijkstra"
},
{
"quote": "Any code of your own that you haven't looked at for six or more months might as well have been written by someone else.",
"author": "Eagleson's Law"
},
{
"quote": "Do one thing every day that scares you.",
"author": "Eleanor Roosevelt"
},
{
"quote": "With the new day comes new strength and new thoughts.",
"author": "Eleanor Roosevelt"
},
{
"quote": "You must do the things you think you cannot do.",
"author": "Eleanor Roosevelt"
},
{
"quote": "Light tomorrow with today.",
"author": "Elizabeth Barrett Browning"
},
{
"quote": "Forever is composed of nows.",
"author": "Emily Dickinson"
},
{
"quote": "Computer science education cannot make anybody an expert programmer any more than studying brushes and pigment can make somebody an expert painter.",
"author": "Eric Raymond"
},
{
"quote": "If you don't risk anything, you risk even more.",
"author": "Erica Jong"
},
{
"quote": "The world breaks everyone, and afterward, many are strong at the broken places.",
"author": "Ernest Hemingway"
},
{
"quote": "There is nothing noble in being superior to your fellow man; true nobility is being superior to your former self.",
"author": "Ernest Hemingway"
},
{
"quote": "Never confuse a single defeat with a final defeat.",
"author": "F. Scott Fitzgerald"
},
{
"quote": "I attribute my success to this - I never gave or took any excuse.",
"author": "Florence Nightingale"
},
{
"quote": "The best revenge is massive success.",
"author": "Frank Sinatra"
},
{
"quote": "The only limit to our realization of tomorrow, will be our doubts of today.",
"author": "Franklin D. Roosevelt"
},
{
"quote": "Right or wrong, it's very pleasant to break something from time to time.",
"author": "Fyodor Dostoevsky"
},
{
"quote": "The harder I work, the luckier I get.",
"author": "Gary Player"
},
{
"quote": "Giving up is the only sure way to fail.",
"author": "Gena Showalter"
},
{
"quote": "The only truly secure system is one that is powered off, cast in a block of concrete and sealed in a lead-lined room with armed guards.",
"author": "Gene Spafford"
},
{
"quote": "A life spent making mistakes is not only more honorable, but more useful than a life spent doing nothing.",
"author": "George Bernard Shaw"
},
{
"quote": "First learn computer science and all the theory. Next develop a programming style. Then forget all that and just hack.",
"author": "George Carrette"
},
{
"quote": "Discovering the unexpected is more important than confirming the known.",
"author": "George Box"
},
{
"quote": "We only see what we know.",
"author": "Goethe"
},
{
"quote": "Without hard work, nothing grows but weeds.",
"author": "Gordon B. Hinckley"
},
{
"quote": "The function of good software is to make the complex appear to be simple.",
"author": "Grady Booch"
},
{
"quote": "When you know that you're capable of dealing with whatever comes, you have the only security the world has to offer.",
"author": "Harry Browne"
},
{
"quote": "Pain is inevitable. Suffering is optional.",
"author": "Haruki Murakami"
},
{
"quote": "Optimism is the faith that leads to achievement. Nothing can be done without hope and confidence.",
"author": "Helen Keller"
},
{
"quote": "The price of anything is the amount of life you exchange for it.",
"author": "Henry David Thoreau"
},
{
"quote": "Whether you think you can or think you can't, you're right.",
"author": "Henry Ford"
},
{
"quote": "The most exciting phrase to hear in science, the one that heralds discoveries, is not 'Eureka!' but 'Now that's funny…'",
"author": "Isaac Asimov"
},
{
"quote": "We are all failures. At least the best of us are.",
"author": "J.M. Barrie"
},
{
"quote": "You can't wait for inspiration. You have to go after it with a club.",
"author": "Jack London"
},
{
"quote": "Don't wish it were easier, wish you were better.",
"author": "Jim Rohn"
},
{
"quote": "By seeking and blundering we learn.",
"author": "Johann Wolfgang von Goethe"
},
{
"quote": "Knowing is not enough; we must apply. Wishing is not enough; we must do.",
"author": "Johann Wolfgang von Goethe"
},
{
"quote": "We first make our habits, then our habits make us.",
"author": "John Dryden"
},
{
"quote": "The power of imagination makes us infinite.",
"author": "John Muir"
},
{
"quote": "May you live every day of your life.",
"author": "Jonathan Swift"
},
{
"quote": "Perseverance is failing 19 times and succeeding the 20th.",
"author": "Julie Andrews"
},
{
"quote": "The work of today is the history of tomorrow, and we are its makers.",
"author": "Juliette Gordon Low"
},
{
"quote": "If you reveal your secrets to the wind, you should not blame the wind for revealing them to the trees.",
"author": "Kahlil Gibran"
},
{
"quote": "Optimism is an occupational hazard of programming; feedback is the treatment.",
"author": "Kent Beck"
},
{
"quote": "Opportunity does not knock, it presents itself when you beat down the door.",
"author": "Kyle Chandler"
},
{
"quote": "To iterate is human, to recurse divine.",
"author": "Peter Deutsch"
},
{
"quote": "A good traveler has no fixed plans and is not intent on arriving.",
"author": "Lao Tzu"
},
{
"quote": "An ant on the move does more than a dozing ox.",
"author": "Lao Tzu"
},
{
"quote": "Do the difficult things while they are easy and do the great things while they are small. A journey of a thousand miles must begin with a single step.",
"author": "Lao Tzu"
},
{
"quote": "That's the thing about people who think they hate computers. What they really hate is lousy programmers.",
"author": "Larry Niven"
},
{
"quote": "It had long since come to my attention that people of accomplishment rarely sat back and let things happen to them. They went out and happened to things.",
"author": "Leonardo da Vinci"
},
{
"quote": "If you're any good at all, you know you can be better.",
"author": "Lindsay Buckingham"
},
{
"quote": "If people never did silly things, nothing intelligent would ever get done.",
"author": "Ludwig Wittgenstein"
},
{
"quote": "You only live once, but if you do it right, once is enough.",
"author": "Mae West"
},
{
"quote": "Live as if you were to die tomorrow. Learn as if you were to live forever.",
"author": "Mahatma Gandhi"
},
{
"quote": "Strength does not come from physical capacity. It comes from an indomitable will.",
"author": "Mahatma Gandhi"
},
{
"quote": "One person's 'paranoia' is another person's 'engineering redundancy'.",
"author": "Marcus J. Ranum"
},
{
"quote": "Nothing in life is to be feared, it is only to be understood. Now is the time to understand more, so that we may fear less.",
"author": "Marie Curie"
},
{
"quote": "If you have everything under control, you're not moving fast enough.",
"author": "Mario Andretti"
},
{
"quote": "Education: the path from cocky ignorance to miserable uncertainty.",
"author": "Mark Twain"
},
{
"quote": "It ain't what you don't know that gets you into trouble. It's what you know for sure that just ain't so.",
"author": "Mark Twain"
},
{
"quote": "The secret of getting ahead is getting started.",
"author": "Mark Twain"
},
{
"quote": "The two most important days in your life are the day you are born and the day you find out why.",
"author": "Mark Twain"
},
{
"quote": "Twenty years from now you will be more disappointed by the things that you didn't do than by the ones you did do. So throw off the bowlines. Sail away from the safe harbor. Catch the trade winds in your sails.",
"author": "Mark Twain"
},
{
"quote": "Any fool can write code that a computer can understand. Good programmers write code that humans can understand.",
"author": "Martin Fowler"
},
{
"quote": "I know, somehow, that only when it is dark enough can you see the stars.",
"author": "Martin Luther King Jr."
},
{
"quote": "It is never too late to be what you might have been.",
"author": "Mary Anne Evans"
},
{
"quote": "Nothing will work unless you do.",
"author": "Maya Angelou"
},
{
"quote": "We delight in the beauty of the butterfly, but rarely admit the changes it has gone through to achieve that beauty.",
"author": "Maya Angelou"
},
{
"quote": "We may encounter many defeats, but we must not be defeated.",
"author": "Maya Angelou"
},
{
"quote": "Everybody has talent, but ability takes hard work.",
"author": "Michael Jordan"
},
{
"quote": "I've missed more than 9,000 shots during my career. I've lost almost 300 games. 26 times, I've been trusted to take the game winning shot and missed. I've failed over and over and over again in my life. And that is why I succeed.",
"author": "Michael Jordan"
},
{
"quote": "Impossible is just a big word thrown around by small men who find it easier to live in the world they've been given than to explore the power they have to change it. Impossible is not a fact. It's an opinion. Impossible is not a declaration. It's a dare. Impossible is potential. Impossible is temporary. Impossible is nothing.",
"author": "Muhammad Ali"
},
{
"quote": "A winner is a dreamer who never gives up.",
"author": "Nelson Mandela"
},
{
"quote": "It always seems impossible until it's done.",
"author": "Nelson Mandela"
},
{
"quote": "Failure will never overtake me if my determination to succeed is strong enough.",
"author": "Og Mandino"
},
{
"quote": "I am not young enough to know everything.",
"author": "Oscar Wilde"
},
{
"quote": "There is only one thing that makes a dream impossible to achieve: the fear of failure.",
"author": "Paulo Coelho"
},
{
"quote": "Never go to bed mad. Stay up and fight.",
"author": "Phyllis Diller"
},
{
"quote": "You can't cross the sea merely by standing and staring at the water.",
"author": "Rabindranath Tagore"
},
{
"quote": "The only person you are destined to become is the person you decide to be.",
"author": "Ralph Waldo Emerson"
},
{
"quote": "What you do speaks so loudly that I cannot hear what you say.",
"author": "Ralph Waldo Emerson"
},
{
"quote": "People who are crazy enough to think they can change the world, are the ones who do.",
"author": "Rob Siltanen"
},
{
"quote": "The best way out is always through.",
"author": "Robert Frost"
},
{
"quote": "Today's accomplishments were yesterday's impossibilities.",
"author": "Robert H. Schuller"
},
{
"quote": "Don't be satisfied with stories, how things have gone with others. Unfold your own myth.",
"author": "Rumi"
},
{
"quote": "Forget safety. Live where you fear to live. Destroy your reputation. Be notorious.",
"author": "Rumi"
},
{
"quote": "Sell your cleverness and buy bewilderment.",
"author": "Rumi"
},
{
"quote": "The cure for pain is in the pain.",
"author": "Rumi"
},
{
"quote": "Have no fear of perfection - you'll never reach it.",
"author": "Salvador Dalí"
},
{
"quote": "Don't watch the clock. Do what it does. Keep going.",
"author": "Sam Levenson"
},
{
"quote": "Ever Tried. Ever failed. No matter. Try again. Fail again. Fail better.",
"author": "Samuel Beckett"
},
{
"quote": "The more you know, the more you realize you know nothing.",
"author": "Socrates"
},
{
"quote": "The greatest enemy of knowledge is not ignorance, it is the illusion of knowledge.",
"author": "Stephen Hawking"
},
{
"quote": "The universe doesn't allow perfection.",
"author": "Stephen Hawking"
},
{
"quote": "Whether you want to uncover the secrets of the universe, or you want to pursue a career in the 21st century, basic computer programming is an essential skill to learn.",
"author": "Stephen Hawking"
},
{
"quote": "The scariest moment is always just before you start.",
"author": "Stephen King"
},
{
"quote": "You can, you should, and if you're brave enough to start, you will.",
"author": "Stephen King"
},
{
"quote": "Arise, Awake and Stop not until the goal is reached.",
"author": "Swami Vivekananda"
},
{
"quote": "It is said that your life flashes before your eyes just before you die. That is true, it's called Life.",
"author": "Terry Pratchett"
},
{
"quote": "Believe you can and you're halfway there.",
"author": "Theodore Roosevelt"
},
{
"quote": "I have not failed. I've just found 10,000 ways that won't work.",
"author": "Thomas A. Edison"
},
{
"quote": "Our greatest weakness lies in giving up. The most certain way to succeed is always to try just one more time.",
"author": "Thomas A. Edison"
},
{
"quote": "The harder the conflict, the more glorious the triumph.",
"author": "Thomas Paine"
},
{
"quote": "The Web as I envisaged it, we have not seen it yet. The future is still so much bigger than the past.",
"author": "Tim Berners-Lee"
},
{
"quote": "Failure is the condiment that gives success its flavor.",
"author": "Truman Capote"
},
{
"quote": "Those who says it cannot be done should not interrupt the person doing it.",
"author": "Unknown"
},
{
"quote": "Even if you fall on your face, you're still moving forward.",
"author": "Victor Kiam"
},
{
"quote": "It's not whether you get knocked down, it's whether you get up.",
"author": "Vince Lombardi"
},
{
"quote": "I dream my painting and I paint my dream.",
"author": "Vincent van Gogh"
},
{
"quote": "Let us cultivate our garden.",
"author": "Voltaire"
},
{
"quote": "Aim for the moon. If you miss, you may hit a star.",
"author": "W. Clement Stone"
},
{
"quote": "The way to get started is to quit talking and begin doing.",
"author": "Walt Disney"
},
{
"quote": "You miss 100% of the shots you don't take.",
"author": "Wayne Gretzky"
},
{
"quote": "Don't let yesterday take up too much of today.",
"author": "Will Rogers"
},
{
"quote": "Even if you're on the right track, you'll get run over if you just sit there.",
"author": "Will Rogers"
},
{
"quote": "Do not wait to strike till the iron is hot; but make it hot by striking.",
"author": "William Butler Yeats"
},
{
"quote": "You cannot swim for new horizons until you have courage to lose sight of the shore.",
"author": "William Faulkner"
},
{
"quote": "Be not afraid of greatness. Some are born great, some achieve greatness, and others have greatness thrust upon them.",
"author": "William Shakespeare"
},
{
"quote": "We know what we are, but not what we may be.",
"author": "William Shakespeare"
},
{
"quote": "In theory there is no difference between theory and practice. In practice there is.",
"author": "Yogi Berra"
},
{
"quote": "You can see a lot by just looking.",
"author": "Yogi Berra"
},
{
"quote": "There is no elevator to success, you have to take the stairs.",
"author": "Zig Ziglar"
},
{
"quote": "You don't have to be great to start, but you have to start to be great.",
"author": "Zig Ziglar"
}
]
}

View File

@@ -32,7 +32,6 @@
"profile": "Профіль",
"news": "Новини",
"donate": "Підтримати",
"support-our-nonprofit": "Support our non-profit",
"update-settings": "Змінити налаштування облікового запису",
"sign-me-out": "Вийти з freeCodeCamp",
"flag-user": "Поскаржитись на обліковий запис цього користувача за порушення",
@@ -217,8 +216,7 @@
"tax-exempt-status": "freeCodeCamp — це некомерційна організація, яка підтримується спонсорськими внесками та звільнена від сплати податків 501(c)(3) (Федеральний ідентифікаційний номер платника податків США: 82-0779546)",
"mission-statement": "Ми хочемо допомогти людям безкоштовно навчитися програмувати. Ми досягаємо цього, створюючи тисячі відео, статей та інтерактивних уроків з програмування — вони усі доступні для широкого загалу. Також ми маємо тисячі безплатний навчальних груп freeCodeCamp у всьому світі.",
"donation-initiatives": "Внески до freeCodeCamp йдуть на наші освітні програми та допомагають оплачувати сервери, послуги та персонал.",
"donate-text": "Ви можете",
"donate-link": "зробити внесок, що не підлягає оподаткуванню, тут",
"donate-text": "You can <1>make a tax-deductible donation here</1>.",
"trending-guides": "Популярні статті",
"our-nonprofit": "Наша неприбуткова організація",
"links": {
@@ -287,11 +285,16 @@
"info": "Інформація",
"code": "Код",
"tests": "Тести",
"restart": "Restart",
"restart-step": "Restart Step",
"console": "Console",
"notes": "Notes",
"preview": "Попередній перегляд"
},
"help-translate": "Ми все ще перекладаємо такі сертифікати.",
"help-translate-link": "Допоможіть нам з перекладом.",
"season-greetings": "Season's Greetings to you and your family.",
"season-greetings-fcc": "Season's Greetings from the freeCodeCamp community 🎉",
"if-getting-value": "If you're getting a lot out of freeCodeCamp, now is a great time to donate to support our nonprofit's mission.",
"project-preview-title": "Here's a preview of what you will build"
},
@@ -459,7 +462,7 @@
"username-not-found": "Ми не змогли знайти користувача з ім'ям \"{{username}}\"",
"add-name": "Цей користувач повинен додати своє ім'я до свого облікового запису, щоб інші могли переглянути його сертифікат.",
"not-eligible": "Наразі цей користувач не має права отримати безкоштовний сертифікат freeCodeCamp.org.",
"profile-private": "{{username}} вирішив зробити своє портфоліо конфіденційним. Йому потрібно надати спільний доступ до свого портфоліо, щоб інші мали змогу переглянути його сертифікат.",
"profile-private": "{{username}} has chosen to make their profile private. They will need to make their profile public in order for others to be able to view their certification.",
"certs-private": "{{username}} вирішив зробити свої сертифікати конфіденційними. Йому потрібно надати спільний доступ до своїх сертифікатів, щоб інші могли їх переглянути.",
"not-honest": "{{username}} ще не прийняв нашу Обітницю академічної доброчесності.",
"user-not-certified": "Схоже, що користувач {{username}} не є сертифікованим {{cert}}",

View File

@@ -0,0 +1,65 @@
{
"article0link": "https://www.freecodecamp.org/news/what-is-javascript-javascript-code-explained-in-plain-english/",
"article0title": "What is JavaScript?",
"article1link": "https://www.freecodecamp.org/news/linux-list-processes-how-to-check-running-processes/",
"article1title": "Linux List Processes",
"article2link": "https://www.freecodecamp.org/news/web-page-text-editor-how-to-open-html-code-in-mac-textedit/",
"article2title": "Web Page Text Editor",
"article3link": "https://www.freecodecamp.org/news/what-is-open-source-software-explained-in-plain-english/",
"article3title": "What is Open Source?",
"article4link": "https://www.freecodecamp.org/news/protect-yourself-against-sim-swapping-attacks/",
"article4title": "Sim Swapping Attacks",
"article5link": "https://www.freecodecamp.org/news/rng-meaning-what-does-rng-stand-for-in-gaming/",
"article5title": "RNG Meaning in Gaming",
"article6link": "https://www.freecodecamp.org/news/the-model-view-controller-pattern-mvc-architecture-and-frameworks-explained/",
"article6title": "Model View Controller",
"article7link": "https://www.freecodecamp.org/news/front-end-developer-what-is-front-end-development-explained-in-plain-english/",
"article7title": "Front End Development",
"article8link": "https://www.freecodecamp.org/news/what-is-a-full-stack-developer-back-end-front-end-full-stack-engineer/",
"article8title": "Full Stack Developer?",
"article9link": "https://www.freecodecamp.org/news/javascript-switch-case-js-switch-statement-example/",
"article9title": "JavaScript Switch Case",
"article10link": "https://www.freecodecamp.org/news/bash-sleep-how-to-make-a-shell-script-wait-n-seconds-example-command/",
"article10title": "Bash Sleep",
"article11link": "https://www.freecodecamp.org/news/bash-array-how-to-declare-an-array-of-strings-in-a-bash-script/",
"article11title": "Bash Array",
"article12link": "https://www.freecodecamp.org/news/what-is-a-cv-and-how-is-it-different-from-a-resume/",
"article12title": "What is a CV?",
"article13link": "https://www.freecodecamp.org/news/coding-programs-101-ways-to-learn-to-code-for-free/",
"article13title": "Coding Programs",
"article14link": "https://www.freecodecamp.org/news/how-to-exit-vim/",
"article14title": "How to Exit Vim",
"article15link": "https://www.freecodecamp.org/news/html-line-break-how-to-break-a-line-with-the-html-br-tag/",
"article15title": "HTML Line Break",
"article16link": "https://www.freecodecamp.org/news/how-to-convert-a-string-to-an-int-in-c-tutorial-with-example-code/",
"article16title": "C# String to Int",
"article17link": "https://www.freecodecamp.org/news/logical-fallacies-definition-fallacy-examples/",
"article17title": "Logical fallacies",
"article18link": "https://www.freecodecamp.org/news/javascript-online-html-css-js-code-editor-list-browser-ide-tools/",
"article18title": "JavaScript Online",
"article19link": "https://www.freecodecamp.org/news/sql-case-statement-tutorial-with-when-then-clause-example-queries/",
"article19title": "SQL Case Statement",
"article20link": "https://www.freecodecamp.org/news/javascript-tolowercase-how-to-convert-a-string-to-lowercase-and-uppercase-in-js/",
"article20title": "JavaScript toLowerCase",
"article21link": "https://www.freecodecamp.org/news/angular-ngclass-example/",
"article21title": "Angular NgClass Example",
"article22link": "https://www.freecodecamp.org/news/sql-aggregate-functions-with-example-data-queries-for-beginners/",
"article22title": "SQL Aggregate Functions",
"article23link": "https://www.freecodecamp.org/news/what-is-web-development-how-to-become-a-web-developer-career-path/",
"article23title": "What is Web Development?",
"article24link": "https://www.freecodecamp.org/news/the-best-way-to-learn-python-python-programming-tutorial-for-beginners/",
"article24title": "Best Way to Learn Python",
"article25link": "https://www.freecodecamp.org/news/word-count-in-google-docs-tutorial-counting-words-and-characters-in-a-google-doc-or-word-file/",
"article25title": "Word Count in Google Docs",
"article26link": "https://www.freecodecamp.org/news/how-to-use-node-environment-variables-with-a-dotenv-file-for-node-js-and-npm/",
"article26title": "Node Environment Variables",
"article27link": "https://www.freecodecamp.org/news/event-viewer-how-to-access-the-windows-10-activity-log/",
"article27title": "Event Viewer in Windows 10",
"article28link": "https://www.freecodecamp.org/news/combine-first-last-names-excel/",
"article28title": "Combine 1st/Last Name Excel",
"article29link": "https://www.freecodecamp.org/news/javascript-if-else-and-if-then-js-conditional-statements/",
"article29title": "JavaScript if-else & if-then"
}

View File

@@ -24,7 +24,7 @@
"build:workers": "cross-env NODE_OPTIONS=\"--max-old-space-size=7168\" webpack --config ./webpack-workers.js",
"clean": "gatsby clean",
"predevelop": "tsc -p ../tools/ && node ../tools/scripts/build/ensure-env.js && npm run build:workers -- --env development",
"develop": "cross-env NODE_OPTIONS=\"--max-old-space-size=4000\" gatsby develop --inspect=9230",
"develop": "cross-env NODE_OPTIONS=\"--max-old-space-size=5000\" gatsby develop --inspect=9230",
"lint": "node ./i18n/schema-validation.js",
"serve": "gatsby serve -p 8000",
"serve-ci": "serve -l 8000 -c ../serve.json public",
@@ -33,12 +33,12 @@
"validate-keys": "tsc -p ../tools/ && node ../tools/scripts/lint/validate-keys.js"
},
"dependencies": {
"@babel/plugin-proposal-export-default-from": "7.16.0",
"@babel/plugin-proposal-function-bind": "7.16.0",
"@babel/plugin-proposal-export-default-from": "7.16.5",
"@babel/plugin-proposal-function-bind": "7.16.5",
"@babel/polyfill": "7.12.1",
"@babel/preset-env": "7.16.4",
"@babel/preset-react": "7.16.0",
"@babel/standalone": "7.16.4",
"@babel/preset-env": "7.16.5",
"@babel/preset-react": "7.16.5",
"@babel/standalone": "7.16.6",
"@fortawesome/fontawesome": "1.1.8",
"@fortawesome/fontawesome-svg-core": "1.2.36",
"@fortawesome/free-brands-svg-icons": "5.15.4",
@@ -48,10 +48,10 @@
"@freecodecamp/react-bootstrap": "0.32.3",
"@freecodecamp/react-calendar-heatmap": "1.0.0",
"@freecodecamp/strip-comments": "3.0.1",
"@loadable/component": "5.15.0",
"@loadable/component": "5.15.2",
"@reach/router": "1.3.4",
"@stripe/react-stripe-js": "1.6.0",
"@stripe/stripe-js": "1.21.2",
"@stripe/react-stripe-js": "1.7.0",
"@stripe/stripe-js": "1.22.0",
"@types/react-scrollable-anchor": "0.6.1",
"algoliasearch": "4.11.0",
"assert": "2.0.0",
@@ -86,7 +86,7 @@
"nanoid": "3.1.30",
"normalize-url": "4.5.1",
"path-browserify": "1.0.1",
"postcss": "8.4.4",
"postcss": "8.4.5",
"prismjs": "1.25.0",
"process": "0.11.10",
"prop-types": "15.7.2",
@@ -98,8 +98,8 @@
"react-ga": "3.3.0",
"react-helmet": "6.1.0",
"react-hotkeys": "2.0.0",
"react-i18next": "11.14.3",
"react-instantsearch-dom": "6.17.0",
"react-i18next": "11.15.1",
"react-instantsearch-dom": "6.18.0",
"react-lazy-load": "3.1.13",
"react-monaco-editor": "0.40.0",
"react-redux": "5.1.2",
@@ -123,14 +123,14 @@
"store": "2.0.12",
"stream-browserify": "3.0.0",
"tone": "14.7.77",
"typescript": "4.5.3",
"typescript": "4.5.4",
"uuid": "8.3.2",
"validator": "13.7.0"
},
"devDependencies": {
"@babel/types": "7.16.0",
"@codesee/babel-plugin-instrument": "0.150.0",
"@codesee/tracker": "0.150.0",
"@codesee/babel-plugin-instrument": "0.153.0",
"@codesee/tracker": "0.153.0",
"@testing-library/jest-dom": "5.16.1",
"@testing-library/react": "12.1.2",
"autoprefixer": "10.4.0",

View File

@@ -1,4 +1,5 @@
const crypto = require('crypto');
const { blockNameify } = require('../../../utils/block-nameify');
function createChallengeNode(challenge, reporter) {
// challengeType 11 is for video challenges (they only have instructions)
@@ -31,6 +32,17 @@ function createChallengeNode(challenge, reporter) {
type: challenge.challengeType === 7 ? 'CertificateNode' : 'ChallengeNode'
};
if (internal.type === 'ChallengeNode') {
const { tests = [], block, dashedName, superBlock } = challenge;
const slug = `/learn/${superBlock}/${block}/${dashedName}`;
challenge.fields = {
slug,
blockName: blockNameify(block),
tests
};
}
return JSON.parse(
JSON.stringify(
Object.assign(
@@ -41,7 +53,8 @@ function createChallengeNode(challenge, reporter) {
internal,
sourceInstanceName: 'challenge'
},
challenge
{ challenge },
{ id: crypto.randomUUID() }
)
)
);

View File

@@ -1,136 +1,158 @@
const mockChallengeNodes = [
{
fields: {
slug: '/super-block-one/block-a/challenge-one',
blockName: 'Block A'
},
id: 'a',
block: 'block-a',
title: 'Challenge One',
isPrivate: false,
superBlock: 'super-block-one',
dashedName: 'challenge-one'
challenge: {
fields: {
slug: '/super-block-one/block-a/challenge-one',
blockName: 'Block A'
},
id: 'a',
block: 'block-a',
title: 'Challenge One',
isPrivate: false,
superBlock: 'super-block-one',
dashedName: 'challenge-one'
}
},
{
fields: {
slug: '/super-block-one/block-a/challenge-two',
blockName: 'Block A'
},
id: 'b',
block: 'block-a',
title: 'Challenge Two',
isPrivate: false,
superBlock: 'super-block-one',
dashedName: 'challenge-two'
challenge: {
fields: {
slug: '/super-block-one/block-a/challenge-two',
blockName: 'Block A'
},
id: 'b',
block: 'block-a',
title: 'Challenge Two',
isPrivate: false,
superBlock: 'super-block-one',
dashedName: 'challenge-two'
}
},
{
fields: {
slug: '/super-block-one/block-b/challenge-one',
blockName: 'Block B'
},
id: 'c',
block: 'block-b',
title: 'Challenge One',
isPrivate: false,
superBlock: 'super-block-one',
dashedName: 'challenge-one'
challenge: {
fields: {
slug: '/super-block-one/block-b/challenge-one',
blockName: 'Block B'
},
id: 'c',
block: 'block-b',
title: 'Challenge One',
isPrivate: false,
superBlock: 'super-block-one',
dashedName: 'challenge-one'
}
},
{
fields: {
slug: '/super-block-one/block-b/challenge-two',
blockName: 'Block B'
},
challenge: {
fields: {
slug: '/super-block-one/block-b/challenge-two',
blockName: 'Block B'
},
id: 'd',
block: 'block-b',
title: 'Challenge Two',
isPrivate: false,
superBlock: 'super-block-one',
dashedName: 'challenge-two'
id: 'd',
block: 'block-b',
title: 'Challenge Two',
isPrivate: false,
superBlock: 'super-block-one',
dashedName: 'challenge-two'
}
},
{
fields: {
slug: '/super-block-one/block-c/challenge-one',
blockName: 'Block C'
},
id: 'e',
block: 'block-c',
title: 'Challenge One',
isPrivate: true,
superBlock: 'super-block-one',
dashedName: 'challenge-one'
challenge: {
fields: {
slug: '/super-block-one/block-c/challenge-one',
blockName: 'Block C'
},
id: 'e',
block: 'block-c',
title: 'Challenge One',
isPrivate: true,
superBlock: 'super-block-one',
dashedName: 'challenge-one'
}
},
{
fields: {
slug: '/super-block-two/block-a/challenge-one',
blockName: 'Block A'
},
id: 'f',
block: 'block-a',
title: 'Challenge One',
isPrivate: false,
superBlock: 'super-block-two',
dashedName: 'challenge-one'
challenge: {
fields: {
slug: '/super-block-two/block-a/challenge-one',
blockName: 'Block A'
},
id: 'f',
block: 'block-a',
title: 'Challenge One',
isPrivate: false,
superBlock: 'super-block-two',
dashedName: 'challenge-one'
}
},
{
fields: {
slug: '/super-block-two/block-a/challenge-two',
blockName: 'Block A'
},
id: 'g',
block: 'block-a',
title: 'Challenge Two',
isPrivate: false,
superBlock: 'super-block-two',
dashedName: 'challenge-two'
challenge: {
fields: {
slug: '/super-block-two/block-a/challenge-two',
blockName: 'Block A'
},
id: 'g',
block: 'block-a',
title: 'Challenge Two',
isPrivate: false,
superBlock: 'super-block-two',
dashedName: 'challenge-two'
}
},
{
fields: {
slug: '/super-block-two/block-b/challenge-one',
blockName: 'Block B'
},
id: 'h',
block: 'block-b',
title: 'Challenge One',
isPrivate: false,
superBlock: 'super-block-two',
dashedName: 'challenge-one'
challenge: {
fields: {
slug: '/super-block-two/block-b/challenge-one',
blockName: 'Block B'
},
id: 'h',
block: 'block-b',
title: 'Challenge One',
isPrivate: false,
superBlock: 'super-block-two',
dashedName: 'challenge-one'
}
},
{
fields: {
slug: '/super-block-two/block-b/challenge-two',
blockName: 'Block B'
},
id: 'i',
block: 'block-b',
title: 'Challenge Two',
isPrivate: false,
superBlock: 'super-block-two',
dashedName: 'challenge-two'
challenge: {
fields: {
slug: '/super-block-two/block-b/challenge-two',
blockName: 'Block B'
},
id: 'i',
block: 'block-b',
title: 'Challenge Two',
isPrivate: false,
superBlock: 'super-block-two',
dashedName: 'challenge-two'
}
},
{
fields: {
slug: '/super-block-three/block-a/challenge-one',
blockName: 'Block A'
},
id: 'j',
block: 'block-a',
title: 'Challenge One',
isPrivate: false,
superBlock: 'super-block-three',
dashedName: 'challenge-one'
challenge: {
fields: {
slug: '/super-block-three/block-a/challenge-one',
blockName: 'Block A'
},
id: 'j',
block: 'block-a',
title: 'Challenge One',
isPrivate: false,
superBlock: 'super-block-three',
dashedName: 'challenge-one'
}
},
{
fields: {
slug: '/super-block-three/block-c/challenge-two',
blockName: 'Block C'
},
id: 'k',
block: 'block-c',
title: 'Challenge Two',
isPrivate: false,
superBlock: 'super-block-three',
dashedName: 'challenge-two'
challenge: {
fields: {
slug: '/super-block-three/block-c/challenge-two',
blockName: 'Block C'
},
id: 'k',
block: 'block-c',
title: 'Challenge Two',
isPrivate: false,
superBlock: 'super-block-three',
dashedName: 'challenge-two'
}
}
];

View File

@@ -8,15 +8,15 @@ function GreenPass(
return (
<>
<span className='sr-only'>{t('icons.passed')}</span>
<svg
aria-label={t('icons.passed')}
height='50'
viewBox='0 0 200 200'
width='50'
xmlns='http://www.w3.org/2000/svg'
{...props}
>
<g>
<g aria-hidden='true'>
<title>{t('icons.passed')}</title>
<circle
cx='100'

View File

@@ -65,6 +65,7 @@ const ShowProjectLinks = (props: ShowProjectLinksProps): JSX.Element => {
return (
<button
className='project-link-button-override'
data-cy={`${projectTitle} solution`}
onClick={onClickHandler}
>
{t('certification.project.solution')}
@@ -97,11 +98,7 @@ const ShowProjectLinks = (props: ShowProjectLinksProps): JSX.Element => {
);
}
return (
<button
className='project-link-button-override'
data-cy={`${projectTitle} solution`}
onClick={onClickHandler}
>
<button className='project-link-button-override' onClick={onClickHandler}>
{t('certification.project.solution')}
</button>
);

View File

@@ -16,6 +16,7 @@ import {
} from '../../../../config/donation-settings';
import {
isSignedInSelector,
isDonatingSelector,
signInLoadingSelector,
donationFormStateSelector,
addDonation,
@@ -70,6 +71,7 @@ type DonateFormProps = {
donationFormState: DonateFormState;
isMinimalForm?: boolean;
isSignedIn: boolean;
isDonating: boolean;
showLoading: boolean;
t: (
label: string,
@@ -82,15 +84,18 @@ type DonateFormProps = {
const mapStateToProps = createSelector(
signInLoadingSelector,
isSignedInSelector,
isDonatingSelector,
donationFormStateSelector,
userSelector,
(
showLoading: DonateFormProps['showLoading'],
isSignedIn: DonateFormProps['isSignedIn'],
isDonating: DonateFormProps['isDonating'],
donationFormState: DonateFormState,
{ email, theme }: { email: string; theme: Themes }
) => ({
isSignedIn,
isDonating,
showLoading,
donationFormState,
email,
@@ -310,7 +315,8 @@ class DonateForm extends Component<DonateFormProps, DonateFormComponentState> {
theme,
t,
isMinimalForm,
isSignedIn
isSignedIn,
isDonating
} = this.props;
const priorityTheme = defaultTheme ? defaultTheme : theme;
const isOneTime = donationDuration === 'onetime';
@@ -318,6 +324,7 @@ class DonateForm extends Component<DonateFormProps, DonateFormComponentState> {
isOneTime ? 'donate.wallet-label' : 'donate.wallet-label-1',
{ usd: donationAmount / 100 }
)}:`;
const showMinimalPayments = isSignedIn && (isMinimalForm || !isDonating);
return (
<>
@@ -342,14 +349,16 @@ class DonateForm extends Component<DonateFormProps, DonateFormComponentState> {
donationDuration={donationDuration}
handlePaymentButtonLoad={this.handlePaymentButtonLoad}
handleProcessing={handleProcessing}
isMinimalForm={isMinimalForm}
isMinimalForm={showMinimalPayments}
isPaypalLoading={loading.paypal}
isSignedIn={isSignedIn}
onDonationStateChange={this.onDonationStateChange}
theme={priorityTheme}
/>
<PatreonButton postPatreonRedirect={this.postPatreonRedirect} />
{isMinimalForm && (
{(!loading.stripe || !loading.paypal) && (
<PatreonButton postPatreonRedirect={this.postPatreonRedirect} />
)}
{showMinimalPayments && (
<>
<div className='separator'>{t('donate.or-card')}</div>
<StripeCardForm

View File

@@ -25,13 +25,12 @@ exports[`<Footer /> matches snapshot 1`] = `
<p
className="footer-donation"
>
footer.donate-text
You can
<a
className="inline"
href="/donate"
>
footer.donate-link
make a tax-deductible donation here
</a>
.
</p>

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Trans, useTranslation } from 'react-i18next';
import Link from '../helpers/link';
import './footer.css';
@@ -15,11 +15,13 @@ function Footer(): JSX.Element {
<p>{t('footer.mission-statement')}</p>
<p>{t('footer.donation-initiatives')}</p>
<p className='footer-donation'>
{t('footer.donate-text')}{' '}
<Link className='inline' to='/donate'>
{t('footer.donate-link')}
</Link>
.
<Trans i18nKey='footer.donate-text'>
You can
<Link className='inline' to='/donate'>
make a tax-deductible donation here
</Link>
.
</Trans>
</p>
</div>
<div className='trending-guides'>

View File

@@ -92,7 +92,7 @@ const Intro = ({
sameTab={false}
to='/donate'
>
{t('buttons.support-our-nonprofit')}
{t('buttons.donate')}
</Link>
</p>
</Alert>

View File

@@ -42,19 +42,19 @@ const linkSpacingStyle = {
function renderLandingMap(nodes: ChallengeNode[]) {
nodes = nodes.filter(
node => node.superBlock !== SuperBlocks.CodingInterviewPrep
({ challenge }) => challenge.superBlock !== SuperBlocks.CodingInterviewPrep
);
return (
<ul data-test-label='certifications'>
{nodes.map((node, i) => (
{nodes.map(({ challenge }, i) => (
<li key={i}>
<Link
className='btn link-btn btn-lg'
to={`/learn/${node.superBlock}/`}
to={`/learn/${challenge.superBlock}/`}
>
<div style={linkSpacingStyle}>
{generateIconComponent(node.superBlock, 'map-icon')}
{i18next.t(`intro:${node.superBlock}.title`)}
{generateIconComponent(challenge.superBlock, 'map-icon')}
{i18next.t(`intro:${challenge.superBlock}.title`)}
</div>
<LinkButton />
</Link>
@@ -68,18 +68,20 @@ function renderLearnMap(
nodes: ChallengeNode[],
currentSuperBlock: MapProps['currentSuperBlock']
) {
nodes = nodes.filter(node => node.superBlock !== currentSuperBlock);
nodes = nodes.filter(
({ challenge }) => challenge.superBlock !== currentSuperBlock
);
return curriculumLocale === 'english' ? (
<ul data-test-label='learn-curriculum-map'>
{nodes.map((node, i) => (
{nodes.map(({ challenge }, i) => (
<li key={i}>
<Link
className='btn link-btn btn-lg'
to={`/learn/${node.superBlock}/`}
to={`/learn/${challenge.superBlock}/`}
>
<div style={linkSpacingStyle}>
{generateIconComponent(node.superBlock, 'map-icon')}
{createSuperBlockTitle(node.superBlock)}
{generateIconComponent(challenge.superBlock, 'map-icon')}
{createSuperBlockTitle(challenge.superBlock)}
</div>
</Link>
</li>
@@ -88,16 +90,18 @@ function renderLearnMap(
) : (
<ul data-test-label='learn-curriculum-map'>
{nodes
.filter(node => isAuditedCert(curriculumLocale, node.superBlock))
.map((node, i) => (
.filter(({ challenge }) =>
isAuditedCert(curriculumLocale, challenge.superBlock)
)
.map(({ challenge }, i) => (
<li key={i}>
<Link
className='btn link-btn btn-lg'
to={`/learn/${node.superBlock}/`}
to={`/learn/${challenge.superBlock}/`}
>
<div style={linkSpacingStyle}>
{generateIconComponent(node.superBlock, 'map-icon')}
{createSuperBlockTitle(node.superBlock)}
{generateIconComponent(challenge.superBlock, 'map-icon')}
{createSuperBlockTitle(challenge.superBlock)}
</div>
</Link>
</li>
@@ -115,16 +119,19 @@ function renderLearnMap(
<Spacer />
</div>
{nodes
.filter(node => !isAuditedCert(curriculumLocale, node.superBlock))
.map((node, i) => (
.filter(
({ challenge }) =>
!isAuditedCert(curriculumLocale, challenge.superBlock)
)
.map(({ challenge }, i) => (
<li key={i}>
<Link
className='btn link-btn btn-lg'
to={`/learn/${node.superBlock}/`}
to={`/learn/${challenge.superBlock}/`}
>
<div style={linkSpacingStyle}>
{generateIconComponent(node.superBlock, 'map-icon')}
{createSuperBlockTitle(node.superBlock)}
{generateIconComponent(challenge.superBlock, 'map-icon')}
{createSuperBlockTitle(challenge.superBlock)}
</div>
</Link>
</li>
@@ -145,12 +152,14 @@ export function Map({
const data: MapData = useStaticQuery(graphql`
query SuperBlockNodes {
allChallengeNode(
sort: { fields: [superOrder] }
filter: { order: { eq: 0 }, challengeOrder: { eq: 0 } }
sort: { fields: [challenge___superOrder] }
filter: { challenge: { order: { eq: 0 }, challengeOrder: { eq: 0 } } }
) {
nodes {
superBlock
dashedName
challenge {
superBlock
dashedName
}
}
}
}

View File

@@ -269,11 +269,13 @@ function useIdToNameMap(): Map<string, string> {
allChallengeNode {
edges {
node {
fields {
slug
challenge {
fields {
slug
}
id
title
}
id
title
}
}
}
@@ -289,12 +291,14 @@ function useIdToNameMap(): Map<string, string> {
edges.forEach(
({
node: {
// @ts-expect-error Graphql needs typing
id,
// @ts-expect-error Graphql needs typing
title,
// @ts-expect-error Graphql needs typing
fields: { slug }
challenge: {
// @ts-expect-error Graphql needs typing
id,
// @ts-expect-error Graphql needs typing
title,
// @ts-expect-error Graphql needs typing
fields: { slug }
}
}
}) => {
idToNameMap.set(id, { challengeTitle: title, challengePath: slug });

View File

@@ -12,29 +12,35 @@ beforeEach(() => {
edges: [
{
node: {
fields: {
slug: ''
},
id: '5e46f802ac417301a38fb92b',
title: 'Page View Time Series Visualizer'
challenge: {
fields: {
slug: ''
},
id: '5e46f802ac417301a38fb92b',
title: 'Page View Time Series Visualizer'
}
}
},
{
node: {
fields: {
slug: ''
},
id: '5e4f5c4b570f7e3a4949899f',
title: 'Sea Level Predictor'
challenge: {
fields: {
slug: ''
},
id: '5e4f5c4b570f7e3a4949899f',
title: 'Sea Level Predictor'
}
}
},
{
node: {
fields: {
slug: ''
},
id: '5e46f7f8ac417301a38fb92a',
title: 'Medical Data Visualizer'
challenge: {
fields: {
slug: ''
},
id: '5e46f7f8ac417301a38fb92a',
title: 'Medical Data Visualizer'
}
}
}
]

View File

@@ -53,7 +53,9 @@ interface LearnPageProps {
user: User;
data: {
challengeNode: {
fields: Slug;
challenge: {
fields: Slug;
};
};
};
executeGA: (payload: Record<string, unknown>) => void;
@@ -70,7 +72,9 @@ function LearnPage({
executeGA,
data: {
challengeNode: {
fields: { slug }
challenge: {
fields: { slug }
}
}
}
}: LearnPageProps) {
@@ -117,9 +121,11 @@ export default connect(mapStateToProps, mapDispatchToProps)(LearnPage);
export const query = graphql`
query FirstChallenge {
challengeNode(order: { eq: 0 }, challengeOrder: { eq: 0 }) {
fields {
slug
challengeNode(challenge: { order: { eq: 0 }, challengeOrder: { eq: 0 } }) {
challenge {
fields {
slug
}
}
}
}

View File

@@ -1,6 +1,7 @@
---
title: Back End Development and APIs
superBlock: back-end-development-and-apis
certification: back-end-development-and-apis
---
## Introduction to Back End Development and APIs

View File

@@ -1,6 +1,7 @@
---
title: Coding Interview Prep
superBlock: coding-interview-prep
certification: coding-interview-prep
---
## Introduction to Coding Interview Prep

View File

@@ -1,6 +1,7 @@
---
title: Data Analysis with Python
superBlock: data-analysis-with-python
certification: data-analysis-with-python
---
## Introduction to Data Analysis with Python

View File

@@ -1,6 +1,7 @@
---
title: Data Visualization
superBlock: data-visualization
certification: data-visualization
---
## Introduction to Data Visualization

View File

@@ -1,6 +1,7 @@
---
title: Front End Development Libraries
superBlock: front-end-development-libraries
certification: front-end-development-libraries
---
## Introduction to Front End Development Libraries

View File

@@ -1,6 +1,7 @@
---
title: Information Security
superBlock: information-security
certification: information-security
---
## Introduction to Information Security

View File

@@ -1,6 +1,7 @@
---
title: JavaScript Algorithms and Data Structures
superBlock: javascript-algorithms-and-data-structures
certification: javascript-algorithms-and-data-structures
---
## Introduction to JavaScript Algorithms and Data Structures

View File

@@ -1,6 +1,7 @@
---
title: Machine Learning with Python
superBlock: machine-learning-with-python
certification: machine-learning-with-python
---
## Introduction to Machine Learning with Python

View File

@@ -1,6 +1,7 @@
---
title: Quality Assurance
superBlock: quality-assurance
certification: quality-assurance
---
## Introduction to Quality Assurance

View File

@@ -1,6 +1,7 @@
---
title: Relational Databases
superBlock: relational-databases
certification: relational-databases
---
## Introduction to Relational Databases

View File

@@ -0,0 +1,20 @@
---
title: Responsive Web Design
superBlock: responsive-web-design-22
certification: responsive-web-design
---
## Introduction to Responsive Web Design
Welcome to Responsive Web Design!
Let's first take a look at what is in the scope of Responsive Web Design (and what is not).
The Web part of Responsive Web Design means that the content you are creating is designed to be delivered over the web to users' browsers. To do this, you will need to learn the language that browsers use to describe web pages: HTML (Hypertext Markup Language) for content, and CSS (Cascading Style Sheets) for design.
That brings us to design. Web Design focuses on how websites should look and behave to the end user. There are plenty of additional aspects to websites, including how to host a website so people can access it, how to protect your websites and store your users' data, and the ethos behind what should go on a website in the first place. These are somewhat harder to teach, but do require a good understanding of the capabilities of the web in modern contexts.
And what of the word Responsive? Well, that brings us to the idea of modern contexts. In today's world, websites are expected to be just as accessible on laptops, tablets and phones. If you compare your user experience on a document-editing program on a computer versus a document-editing program on a mobile, the features on mobile are likely to be far more constrained. This applies in some way to websites as well, but developers have found interesting ways to adapt to the mobile experience, not in the least by harnessing powerful features built for flexible (a.k.a responsive) web displays such as CSS `flexbox` and CSS `grid`.
Ready to learn about good design practices for the web? Let's get started!
[Begin Course](https://www.freecodecamp.org/learn/responsive-web-design/basic-html-and-html5/)

View File

@@ -1,6 +1,7 @@
---
title: Responsive Web Design
superBlock: responsive-web-design
certification: responsive-web-design
---
## Introduction to Responsive Web Design

View File

@@ -1,6 +1,7 @@
---
title: Scientific Computing with Python
superBlock: scientific-computing-with-python
certification: scientific-computing-with-python
---
## Introduction to Scientific Computing with Python

View File

@@ -86,6 +86,8 @@ export type MarkdownRemark = {
block: string;
isBeta: boolean;
superBlock: SuperBlocks;
// TODO: make enum like superBlock
certification: string;
title: typeof certMap[number]['title'];
};
headings: [
@@ -128,55 +130,59 @@ export interface VideoLocaleIds {
}
export type ChallengeNode = {
block: string;
challengeOrder: number;
challengeType: number;
dashedName: string;
description: string;
challengeFiles: ChallengeFiles;
fields: Fields;
forumTopicId: number;
guideUrl: string;
head: string[];
helpCategory: string;
id: string;
instructions: string;
isComingSoon: boolean;
internal?: {
content: string;
contentDigest: string;
challenge: {
block: string;
certification: string;
challengeOrder: number;
challengeType: number;
dashedName: string;
description: string;
fieldOwners: string[];
ignoreType: boolean | null;
mediaType: string;
owner: string;
type: string;
challengeFiles: ChallengeFiles;
fields: Fields;
forumTopicId: number;
guideUrl: string;
head: string[];
hasEditableBoundaries: boolean;
helpCategory: string;
id: string;
instructions: string;
isComingSoon: boolean;
internal?: {
content: string;
contentDigest: string;
description: string;
fieldOwners: string[];
ignoreType: boolean | null;
mediaType: string;
owner: string;
type: string;
};
notes: string;
removeComments: boolean;
isLocked: boolean;
isPrivate: boolean;
order: number;
question: Question;
required: Required[];
solutions: {
[T in FileKey]: FileKeyChallenge;
};
sourceInstanceName: string;
superOrder: number;
superBlock: SuperBlocks;
tail: string[];
template: string;
tests: Test[];
time: string;
title: string;
translationPending: boolean;
url: string;
usesMultifileEditor: boolean;
videoId: string;
videoLocaleIds?: VideoLocaleIds;
bilibiliIds?: BilibiliIds;
videoUrl: string;
};
notes: string;
removeComments: boolean;
isLocked: boolean;
isPrivate: boolean;
order: number;
question: Question;
required: Required[];
solutions: {
[T in FileKey]: FileKeyChallenge;
};
sourceInstanceName: string;
superOrder: number;
superBlock: SuperBlocks;
tail: string[];
template: string;
tests: Test[];
time: string;
title: string;
translationPending: boolean;
url: string;
usesMultifileEditor: boolean;
videoId: string;
videoLocaleIds?: VideoLocaleIds;
bilibiliIds?: BilibiliIds;
videoUrl: string;
};
export type AllChallengeNode = {

View File

@@ -1,3 +1,4 @@
import { omit } from 'lodash-es';
import { call, delay, put, takeLatest, takeEvery } from 'redux-saga/effects';
import { createFlashMessage } from '../../components/Flash/redux';
@@ -85,7 +86,13 @@ function* verifyCertificationSaga({ payload }) {
yield put(
verifyCertComplete({
...response,
payload: { ...isCertMap, completedChallenges }
payload: {
...isCertMap,
completedChallenges: completedChallenges.map(x => ({
...omit(x, 'files'),
challengeFiles: x.files ?? null
}))
}
})
);
yield put(createFlashMessage(response));

View File

@@ -1,7 +1,11 @@
import { SuperBlocks } from '../../../config/certification-settings';
import envData from '../../../config/env.json';
const { showNewCurriculum } = envData;
const responsiveWebBase =
'/learn/responsive-web-design/responsive-web-design-projects';
const responsiveWeb22Base = '/learn/responsive-web-design-22';
const jsAlgoBase =
'/learn/javascript-algorithms-and-data-structures/' +
'javascript-algorithms-and-data-structures-projects';
@@ -294,38 +298,50 @@ const certMap = [
certSlug: SuperBlocks.RespWebDesign,
flag: 'isRespWebDesignCert',
projects: [
{
id: 'bd7158d8c442eddfaeb5bd18',
title: 'Build a Tribute Page',
link: `${responsiveWebBase}/build-a-tribute-page`,
certSlug: SuperBlocks.RespWebDesign
},
{
id: '587d78af367417b2b2512b03',
title: 'Build a Survey Form',
link: `${responsiveWebBase}/build-a-survey-form`,
link: getResponsiveWebDesignPath('build-a-survey-form', {
showNewCurriculum
}),
certSlug: SuperBlocks.RespWebDesign
},
{
id: '587d78af367417b2b2512b04',
title: 'Build a Product Landing Page',
link: `${responsiveWebBase}/build-a-product-landing-page`,
id: 'bd7158d8c442eddfaeb5bd18',
title: 'Build a Tribute Page',
link: getResponsiveWebDesignPath('build-a-tribute-page', {
showNewCurriculum
}),
certSlug: SuperBlocks.RespWebDesign
},
{
id: '587d78b0367417b2b2512b05',
title: 'Build a Technical Documentation Page',
link: `${responsiveWebBase}/build-a-technical-documentation-page`,
link: getResponsiveWebDesignPath(
'build-a-technical-documentation-page',
{ showNewCurriculum }
),
certSlug: SuperBlocks.RespWebDesign
},
{
id: '587d78af367417b2b2512b04',
title: 'Build a Product Landing Page',
link: getResponsiveWebDesignPath('build-a-product-landing-page', {
showNewCurriculum
}),
certSlug: SuperBlocks.RespWebDesign
},
{
id: 'bd7158d8c242eddfaeb5bd13',
title: 'Build a Personal Portfolio Webpage',
link: `${responsiveWebBase}/build-a-personal-portfolio-webpage`,
link: getResponsiveWebDesignPath('build-a-personal-portfolio-webpage', {
showNewCurriculum
}),
certSlug: SuperBlocks.RespWebDesign
}
]
},
{
id: '561abd10cb81ac38a17513bc',
title: 'JavaScript Algorithms and Data Structures',
@@ -709,6 +725,15 @@ const certMap = [
}
] as const;
function getResponsiveWebDesignPath(
project: string,
{ showNewCurriculum }: { showNewCurriculum: boolean }
) {
return showNewCurriculum
? `${responsiveWeb22Base}/${project}-project/${project}`
: `${responsiveWebBase}/${project}/`;
}
const titles = certMap.map(({ title }) => title);
type Title = typeof titles[number];
const legacyProjectMap: Partial<Record<Title, unknown>> = {};

View File

@@ -7,11 +7,7 @@ import { isDonationModalOpenSelector, userSelector } from '../../../redux';
import {
canFocusEditorSelector,
consoleOutputSelector,
executeChallenge,
saveEditorContent,
setEditorFocusability,
visibleEditorsSelector,
updateFile
visibleEditorsSelector
} from '../redux';
import { getTargetEditor } from '../utils/getTargetEditor';
import './editor.css';
@@ -26,7 +22,6 @@ const propTypes = {
description: PropTypes.string,
dimensions: PropTypes.object,
editorRef: PropTypes.any.isRequired,
executeChallenge: PropTypes.func.isRequired,
ext: PropTypes.string,
fileKey: PropTypes.string,
initialEditorContent: PropTypes.string,
@@ -37,11 +32,9 @@ const propTypes = {
onStopResize: PropTypes.func,
onResize: PropTypes.func
}),
saveEditorContent: PropTypes.func.isRequired,
setEditorFocusability: PropTypes.func,
theme: PropTypes.string,
title: PropTypes.string,
updateFile: PropTypes.func.isRequired,
showProjectPreview: PropTypes.bool,
usesMultifileEditor: PropTypes.bool,
visibleEditors: PropTypes.shape({
scriptjs: PropTypes.bool,
@@ -65,13 +58,6 @@ const mapStateToProps = createSelector(
})
);
const mapDispatchToProps = {
executeChallenge,
saveEditorContent,
setEditorFocusability,
updateFile
};
const MultifileEditor = props => {
const {
challengeFiles,
@@ -83,7 +69,8 @@ const MultifileEditor = props => {
resizeProps,
title,
visibleEditors: { stylescss, indexhtml, scriptjs, indexjsx },
usesMultifileEditor
usesMultifileEditor,
showProjectPreview
} = props;
const editorTheme = theme === 'night' ? 'vs-dark-custom' : 'vs-custom';
// TODO: the tabs mess up the rendering (scroll doesn't work properly and
@@ -145,6 +132,7 @@ const MultifileEditor = props => {
theme={editorTheme}
title={title}
usesMultifileEditor={usesMultifileEditor}
showProjectPreview={showProjectPreview}
/>
</ReflexElement>
);
@@ -159,4 +147,4 @@ const MultifileEditor = props => {
MultifileEditor.displayName = 'MultifileEditor';
MultifileEditor.propTypes = propTypes;
export default connect(mapStateToProps, mapDispatchToProps)(MultifileEditor);
export default connect(mapStateToProps)(MultifileEditor);

View File

@@ -2,15 +2,13 @@ import { first } from 'lodash-es';
import React, { useState, ReactElement } from 'react';
import { ReflexContainer, ReflexSplitter, ReflexElement } from 'react-reflex';
import { sortChallengeFiles } from '../../../../../utils/sort-challengefiles';
import envData from '../../../../../config/env.json';
import {
ChallengeFile,
ChallengeFiles,
ResizeProps
} from '../../../redux/prop-types';
import ActionRow from './action-row';
const { showUpcomingChanges } = envData;
import EditorTabs from './editor-tabs';
type Pane = { flex: number };
@@ -85,7 +83,7 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => {
} = props;
const challengeFile = getChallengeFile();
const projectBasedChallenge = showUpcomingChanges && hasEditableBoundaries;
const projectBasedChallenge = hasEditableBoundaries;
const displayPreview = projectBasedChallenge
? showPreview && hasPreview
: hasPreview;
@@ -124,6 +122,7 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => {
)}
<ReflexElement flex={editorPane.flex} {...resizeProps}>
{challengeFile && !hasEditableBoundaries && <EditorTabs />}
{challengeFile && (
<ReflexContainer
key={challengeFile.fileKey}

View File

@@ -1,6 +1,3 @@
#description {
background: var(--secondary-background);
}
.monaco-editor .margin-view-overlays .line-numbers,
.monaco-editor .margin-view-overlays .myLineDecoration + .line-numbers {
color: var(--primary-color);
@@ -48,6 +45,23 @@
max-width: 700px;
}
.description-highlighter {
background-color: var(--secondary-background);
opacity: 1;
animation-name: example;
animation-duration: 2s;
animation-delay: 0.5s;
}
@keyframes example {
from {
background-color: var(--highlight-background);
}
to {
background-color: var(--secondary-background);
}
}
#description p:last-child {
margin: 0px;
}

View File

@@ -42,7 +42,8 @@ import {
submitChallenge,
initTests,
isResettingSelector,
stopResetting
stopResetting,
isProjectPreviewModalOpenSelector
} from '../redux';
import './editor.css';
@@ -76,6 +77,8 @@ interface EditorProps {
tests: Test[];
theme: Themes;
title: string;
showProjectPreview: boolean;
previewOpen: boolean;
updateFile: (object: {
fileKey: FileKey;
editorValue: string;
@@ -104,6 +107,7 @@ const mapStateToProps = createSelector(
canFocusEditorSelector,
consoleOutputSelector,
isDonationModalOpenSelector,
isProjectPreviewModalOpenSelector,
isResettingSelector,
userSelector,
challengeTestsSelector,
@@ -111,11 +115,13 @@ const mapStateToProps = createSelector(
canFocus: boolean,
output: string[],
open,
previewOpen: boolean,
isResetting: boolean,
{ theme = Themes.Default }: { theme: Themes },
tests: [{ text: string; testString: string }]
) => ({
canFocus: open ? false : canFocus,
previewOpen,
isResetting,
output,
theme,
@@ -895,6 +901,18 @@ const Editor = (props: EditorProps): JSX.Element => {
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.challengeFiles, props.isResetting]);
useEffect(() => {
const { showProjectPreview, previewOpen } = props;
if (!previewOpen && showProjectPreview) {
const description = document.getElementsByClassName(
'description-container'
)[0];
description.classList.add('description-highlighter');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.previewOpen]);
useEffect(() => {
const { output } = props;
const { model, insideEditDecId } = dataRef.current;

View File

@@ -1,52 +1,52 @@
import { TabPane, Tabs } from '@freecodecamp/react-bootstrap';
import i18next from 'i18next';
import React, { Component, ReactElement } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import { createStructuredSelector } from 'reselect';
import envData from '../../../../../config/env.json';
import ToolPanel from '../components/tool-panel';
import { currentTabSelector, moveToTab } from '../redux';
import EditorTabs from './editor-tabs';
const { showUpcomingChanges } = envData;
const mapStateToProps = createStructuredSelector({
currentTab: currentTabSelector as (state: unknown) => number
});
const mapDispatchToProps = (dispatch: Dispatch) =>
bindActionCreators(
{
moveToTab
},
dispatch
);
interface MobileLayoutProps {
currentTab: number;
editor: JSX.Element | null;
guideUrl: string;
hasEditableBoundaries: boolean;
hasNotes: boolean;
hasPreview: boolean;
instructions: JSX.Element;
moveToTab: typeof moveToTab;
notes: ReactElement;
preview: JSX.Element;
testOutput: JSX.Element;
videoUrl: string;
usesMultifileEditor: boolean;
}
class MobileLayout extends Component<MobileLayoutProps> {
enum Tab {
Editor = 'editor',
Preview = 'preview',
Console = 'console',
Notes = 'notes',
Instructions = 'instructions'
}
interface MobileLayoutState {
currentTab: Tab;
}
class MobileLayout extends Component<MobileLayoutProps, MobileLayoutState> {
static displayName: string;
componentDidMount() {
if (this.props.currentTab !== 1) this.props.moveToTab(1);
}
state: MobileLayoutState = {
currentTab: this.props.hasEditableBoundaries ? Tab.Editor : Tab.Instructions
};
switchTab = (tab: Tab) => {
this.setState({
currentTab: tab
});
};
render() {
const { currentTab } = this.state;
const {
currentTab,
moveToTab,
hasEditableBoundaries,
instructions,
editor,
@@ -67,44 +67,41 @@ class MobileLayout extends Component<MobileLayoutProps> {
// Unlike the desktop layout the mobile version does not have an ActionRow,
// but still needs a way to switch between the different tabs.
const projectBasedChallenge = showUpcomingChanges && usesMultifileEditor;
const eventKeys = [1, 2, 3, 4, 5];
return (
<>
<Tabs
activeKey={currentTab}
defaultActiveKey={1}
defaultActiveKey={currentTab}
id='challenge-page-tabs'
onSelect={moveToTab}
onSelect={this.switchTab}
>
{!hasEditableBoundaries && (
<TabPane
eventKey={eventKeys.shift()}
eventKey={Tab.Instructions}
title={i18next.t('learn.editor-tabs.info')}
>
{instructions}
</TabPane>
)}
<TabPane
eventKey={eventKeys.shift()}
eventKey={Tab.Editor}
title={i18next.t('learn.editor-tabs.code')}
{...editorTabPaneProps}
>
{projectBasedChallenge && <EditorTabs />}
{usesMultifileEditor && <EditorTabs />}
{editor}
</TabPane>
<TabPane
eventKey={eventKeys.shift()}
eventKey={Tab.Console}
title={i18next.t('learn.editor-tabs.tests')}
{...editorTabPaneProps}
>
{testOutput}
</TabPane>
{hasNotes && projectBasedChallenge && (
{hasNotes && usesMultifileEditor && (
<TabPane
eventKey={eventKeys.shift()}
eventKey={Tab.Notes}
title={i18next.t('learn.editor-tabs.notes')}
>
{notes}
@@ -112,7 +109,7 @@ class MobileLayout extends Component<MobileLayoutProps> {
)}
{hasPreview && (
<TabPane
eventKey={eventKeys.shift()}
eventKey={Tab.Preview}
title={i18next.t('learn.editor-tabs.preview')}
>
{preview}
@@ -127,4 +124,4 @@ class MobileLayout extends Component<MobileLayoutProps> {
MobileLayout.displayName = 'MobileLayout';
export default connect(mapStateToProps, mapDispatchToProps)(MobileLayout);
export default MobileLayout;

View File

@@ -212,7 +212,9 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
componentDidMount() {
const {
data: {
challengeNode: { title }
challengeNode: {
challenge: { title }
}
}
} = this.props;
this.initializeComponent(title);
@@ -222,16 +224,20 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
const {
data: {
challengeNode: {
title: prevTitle,
fields: { tests: prevTests }
challenge: {
title: prevTitle,
fields: { tests: prevTests }
}
}
}
} = prevProps;
const {
data: {
challengeNode: {
title: currentTitle,
fields: { tests: currTests }
challenge: {
title: currentTitle,
fields: { tests: currTests }
}
}
}
} = this.props;
@@ -250,11 +256,13 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
openModal,
data: {
challengeNode: {
challengeFiles,
fields: { tests },
challengeType,
removeComments,
helpCategory
challenge: {
challengeFiles,
fields: { tests },
challengeType,
removeComments,
helpCategory
}
}
},
pageContext: {
@@ -282,7 +290,7 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
cancelTests();
}
getChallenge = () => this.props.data.challengeNode;
getChallenge = () => this.props.data.challengeNode.challenge;
getBlockNameTitle(t: TFunction) {
const { block, superBlock, title } = this.getChallenge();
@@ -334,11 +342,16 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
renderEditor() {
const {
pageContext: {
projectPreview: { showProjectPreview }
},
challengeFiles,
data: {
challengeNode: {
fields: { tests },
usesMultifileEditor
challenge: {
fields: { tests },
usesMultifileEditor
}
}
}
} = this.props;
@@ -355,6 +368,7 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
resizeProps={this.resizeProps}
title={title}
usesMultifileEditor={usesMultifileEditor}
showProjectPreview={showProjectPreview}
/>
)
);
@@ -388,21 +402,14 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
);
}
hasEditableBoundaries() {
const { challengeFiles } = this.props;
return (
challengeFiles?.some(
challengeFile => challengeFile.editableRegionBoundaries?.length === 2
) ?? false
);
}
render() {
const {
block,
fields: { blockName },
forumTopicId,
hasEditableBoundaries,
superBlock,
certification,
title,
usesMultifileEditor,
notes
@@ -433,7 +440,7 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
<MobileLayout
editor={this.renderEditor()}
guideUrl={getGuideUrl({ forumTopicId, title })}
hasEditableBoundaries={this.hasEditableBoundaries()}
hasEditableBoundaries={hasEditableBoundaries}
hasNotes={!!notes}
hasPreview={this.hasPreview()}
instructions={this.renderInstructionsPanel({
@@ -451,7 +458,7 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
block={block}
challengeFiles={challengeFiles}
editor={this.renderEditor()}
hasEditableBoundaries={this.hasEditableBoundaries()}
hasEditableBoundaries={hasEditableBoundaries}
hasNotes={!!notes}
hasPreview={this.hasPreview()}
instructions={this.renderInstructionsPanel({
@@ -468,6 +475,7 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
<CompletionModal
block={block}
blockName={blockName}
certification={certification}
superBlock={superBlock}
/>
<HelpModal />
@@ -489,40 +497,44 @@ export default connect(
export const query = graphql`
query ClassicChallenge($slug: String!) {
challengeNode(fields: { slug: { eq: $slug } }) {
block
title
description
instructions
notes
removeComments
challengeType
helpCategory
videoUrl
superBlock
translationPending
forumTopicId
fields {
blockName
slug
tests {
text
testString
challengeNode(challenge: { fields: { slug: { eq: $slug } } }) {
challenge {
block
title
description
hasEditableBoundaries
instructions
notes
removeComments
challengeType
helpCategory
videoUrl
superBlock
certification
translationPending
forumTopicId
fields {
blockName
slug
tests {
text
testString
}
}
required {
link
src
}
usesMultifileEditor
challengeFiles {
fileKey
ext
name
contents
head
tail
editableRegionBoundaries
}
}
required {
link
src
}
usesMultifileEditor
challengeFiles {
fileKey
ext
name
contents
head
tail
editableRegionBoundaries
}
}
}

View File

@@ -49,7 +49,9 @@ class ShowCodeAlly extends Component<ShowCodeAllyProps> {
const {
updateChallengeMeta,
data: {
challengeNode: { challengeType, title }
challengeNode: {
challenge: { challengeType, title }
}
},
pageContext: { challengeMeta }
} = this.props;
@@ -60,9 +62,11 @@ class ShowCodeAlly extends Component<ShowCodeAllyProps> {
const {
data: {
challengeNode: {
title,
fields: { blockName },
url
challenge: {
title,
fields: { blockName },
url
}
}
},
webhookToken = null
@@ -94,12 +98,14 @@ export default connect(mapStateToProps, mapDispatchToProps)(ShowCodeAlly);
// GraphQL
export const query = graphql`
query CodeAllyChallenge($slug: String!) {
challengeNode(fields: { slug: { eq: $slug } }) {
title
challengeType
url
fields {
blockName
challengeNode(challenge: { fields: { slug: { eq: $slug } } }) {
challenge {
title
challengeType
url
fields {
blockName
}
}
}
}

View File

@@ -42,12 +42,8 @@ exports[`<ChallengeTitle/> renders correctly 1`] = `
<b>
title text
</b>
<span
className="sr-only"
>
icons.passed
</span>
<svg
aria-label="icons.passed"
height="50"
style={
Object {
@@ -60,7 +56,9 @@ exports[`<ChallengeTitle/> renders correctly 1`] = `
width="50"
xmlns="http://www.w3.org/2000/svg"
>
<g>
<g
aria-hidden="true"
>
<title>
icons.passed
</title>

View File

@@ -5,12 +5,8 @@ exports[`<CompletionModalBody /> matches snapshot 1`] = `
<div
class="completion-challenge-details"
>
<span
class="sr-only"
>
icons.passed
</span>
<svg
aria-label="icons.passed"
class="completion-success-icon"
data-testid="fcc-completion-success-icon"
height="50"
@@ -18,7 +14,9 @@ exports[`<CompletionModalBody /> matches snapshot 1`] = `
width="50"
xmlns="http://www.w3.org/2000/svg"
>
<g>
<g
aria-hidden="true"
>
<title>
icons.passed
</title>
@@ -62,14 +60,17 @@ exports[`<CompletionModalBody /> matches snapshot 1`] = `
intro:responsive-web-design.blocks.basic-html-and-html5.title
</div>
<div
aria-label="learn.percent-complete"
class="progress-bar-wrap"
>
<div
aria-hidden="true"
class="progress-bar-background"
>
learn.percent-complete
</div>
<div
aria-hidden="true"
class="progress-bar-percent"
data-testid="fcc-progress-bar-percent"
style="width: 0%;"

View File

@@ -88,13 +88,19 @@ export class CompletionModalBody extends PureComponent<
</div>
<div className='completion-block-details'>
<div className='completion-block-name'>{blockTitle}</div>
<div className='progress-bar-wrap'>
<div className='progress-bar-background'>
<div
className='progress-bar-wrap'
aria-label={t('learn.percent-complete', {
percent: completedPercent
})}
>
<div className='progress-bar-background' aria-hidden='true'>
{t('learn.percent-complete', {
percent: this.state.shownPercent
})}
</div>
<div
aria-hidden='true'
className='progress-bar-percent'
data-testid='fcc-progress-bar-percent'
style={{ width: `${this.state.shownPercent}%` }}

View File

@@ -10,6 +10,7 @@ import { Dispatch } from 'redux';
import { createSelector } from 'reselect';
import { dasherize } from '../../../../../utils/slugs';
import { isProject } from '../../../../utils/challenge-types';
import Login from '../../../components/Header/components/Login';
import {
isSignedInSelector,
@@ -40,7 +41,11 @@ const mapStateToProps = createSelector(
successMessageSelector,
(
challengeFiles: ChallengeFiles,
{ title, id }: { title: string; id: string },
{
title,
id,
challengeType
}: { title: string; id: string; challengeType: number },
completedChallengesIds: string[],
isOpen: boolean,
isSignedIn: boolean,
@@ -49,6 +54,7 @@ const mapStateToProps = createSelector(
challengeFiles,
title,
id,
challengeType,
completedChallengesIds,
isOpen,
isSignedIn,
@@ -94,6 +100,8 @@ interface CompletionModalsProps {
allowBlockDonationRequests: (arg0: string) => void;
block: string;
blockName: string;
certification: string;
challengeType: number;
close: () => void;
completedChallengesIds: string[];
currentBlockIds?: string[];
@@ -275,34 +283,83 @@ export class CompletionModalInner extends Component<
}
}
const useCurrentBlockIds = (blockName: string) => {
interface Options {
isCertificationBlock: boolean;
}
interface CertificateNode {
challenge: {
// TODO: use enum
certification: string;
tests: { id: string }[];
};
}
const useCurrentBlockIds = (
block: string,
certification: string,
options?: Options
) => {
const {
allChallengeNode: { edges }
}: { allChallengeNode: AllChallengeNode } = useStaticQuery(graphql`
allChallengeNode: { edges: challengeEdges },
allCertificateNode: { nodes: certificateNodes }
}: {
allChallengeNode: AllChallengeNode;
allCertificateNode: { nodes: CertificateNode[] };
} = useStaticQuery(graphql`
query getCurrentBlockNodes {
allChallengeNode(sort: { fields: [superOrder, order, challengeOrder] }) {
allChallengeNode(
sort: {
fields: [
challenge___superOrder
challenge___order
challenge___challengeOrder
]
}
) {
edges {
node {
fields {
blockName
challenge {
block
id
}
}
}
}
allCertificateNode {
nodes {
challenge {
certification
tests {
id
}
id
}
}
}
}
`);
const currentBlockIds = edges
.filter(edge => edge.node.fields.blockName === blockName)
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
.map(edge => edge.node.id);
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return currentBlockIds;
const currentCertificateIds = certificateNodes
.filter(
node => dasherize(node.challenge.certification) === certification
)[0]
?.challenge.tests.map(test => test.id);
const currentBlockIds = challengeEdges
.filter(edge => edge.node.challenge.block === block)
.map(edge => edge.node.challenge.id);
return options?.isCertificationBlock
? currentCertificateIds
: currentBlockIds;
};
const CompletionModal = (props: CompletionModalsProps) => {
const currentBlockIds = useCurrentBlockIds(props.blockName || '');
const currentBlockIds = useCurrentBlockIds(
props.block || '',
props.certification || '',
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
{ isCertificationBlock: isProject(props.challengeType) }
);
return <CompletionModalInner currentBlockIds={currentBlockIds} {...props} />;
};

View File

@@ -123,16 +123,20 @@ class BackEnd extends Component<BackEndProps> {
const {
data: {
challengeNode: {
title: prevTitle,
fields: { tests: prevTests }
challenge: {
title: prevTitle,
fields: { tests: prevTests }
}
}
}
} = prevProps;
const {
data: {
challengeNode: {
title: currentTitle,
fields: { tests: currTests }
challenge: {
title: currentTitle,
fields: { tests: currTests }
}
}
}
} = this.props;
@@ -149,10 +153,12 @@ class BackEnd extends Component<BackEndProps> {
updateChallengeMeta,
data: {
challengeNode: {
fields: { tests },
title,
challengeType,
helpCategory
challenge: {
fields: { tests },
title,
challengeType,
helpCategory
}
}
},
pageContext: { challengeMeta }
@@ -182,15 +188,18 @@ class BackEnd extends Component<BackEndProps> {
const {
data: {
challengeNode: {
fields: { blockName },
challengeType,
forumTopicId,
title,
description,
instructions,
translationPending,
superBlock,
block
challenge: {
fields: { blockName },
challengeType,
forumTopicId,
title,
description,
instructions,
translationPending,
certification,
superBlock,
block
}
}
},
isChallengeCompleted,
@@ -258,6 +267,7 @@ class BackEnd extends Component<BackEndProps> {
<CompletionModal
block={block}
blockName={blockName}
certification={certification}
superBlock={superBlock}
/>
<HelpModal />
@@ -278,22 +288,25 @@ export default connect(
export const query = graphql`
query BackendChallenge($slug: String!) {
challengeNode(fields: { slug: { eq: $slug } }) {
forumTopicId
title
description
instructions
challengeType
helpCategory
superBlock
block
translationPending
fields {
blockName
slug
tests {
text
testString
challengeNode(challenge: { fields: { slug: { eq: $slug } } }) {
challenge {
forumTopicId
title
description
instructions
challengeType
helpCategory
certification
superBlock
block
translationPending
fields {
blockName
slug
tests {
text
testString
}
}
}
}

View File

@@ -76,7 +76,9 @@ class Project extends Component<ProjectProps> {
const {
challengeMounted,
data: {
challengeNode: { title, challengeType, helpCategory }
challengeNode: {
challenge: { title, challengeType, helpCategory }
}
},
pageContext: { challengeMeta },
updateChallengeMeta
@@ -94,13 +96,17 @@ class Project extends Component<ProjectProps> {
componentDidUpdate(prevProps: ProjectProps): void {
const {
data: {
challengeNode: { title: prevTitle }
challengeNode: {
challenge: { title: prevTitle }
}
}
} = prevProps;
const {
challengeMounted,
data: {
challengeNode: { title: currentTitle, challengeType, helpCategory }
challengeNode: {
challenge: { title: currentTitle, challengeType, helpCategory }
}
},
pageContext: { challengeMeta },
updateChallengeMeta
@@ -130,15 +136,18 @@ class Project extends Component<ProjectProps> {
const {
data: {
challengeNode: {
challengeType,
fields: { blockName },
forumTopicId,
title,
description,
instructions,
superBlock,
block,
translationPending
challenge: {
challengeType,
fields: { blockName },
forumTopicId,
title,
description,
instructions,
superBlock,
certification,
block,
translationPending
}
}
},
isChallengeCompleted,
@@ -195,6 +204,7 @@ class Project extends Component<ProjectProps> {
<CompletionModal
block={block}
blockName={blockName}
certification={certification}
superBlock={superBlock}
/>
<HelpModal />
@@ -215,19 +225,22 @@ export default connect(
export const query = graphql`
query ProjectChallenge($slug: String!) {
challengeNode(fields: { slug: { eq: $slug } }) {
forumTopicId
title
description
instructions
challengeType
helpCategory
superBlock
block
translationPending
fields {
blockName
slug
challengeNode(challenge: { fields: { slug: { eq: $slug } } }) {
challenge {
forumTopicId
title
description
instructions
challengeType
helpCategory
superBlock
certification
block
translationPending
fields {
blockName
slug
}
}
}
}

View File

@@ -91,7 +91,7 @@ A required file can not have both a src and a link: src = ${src}, link = ${link}
}
return '';
})
.reduce((head, element) => head.concat(element), []);
.join('\n');
const indexHtml = findIndexHtml(challengeFiles);

View File

@@ -40,8 +40,6 @@ export const actionTypes = createTypes(
'stopResetting',
'submitChallenge',
'moveToTab',
'setEditorFocusability',
'toggleVisibleEditor'
],

View File

@@ -1,4 +1,5 @@
import { navigate } from 'gatsby';
import { omit } from 'lodash-es';
import { ofType } from 'redux-observable';
import { of, empty } from 'rxjs';
import {
@@ -35,16 +36,20 @@ import {
function postChallenge(update, username) {
const saveChallenge = postUpdate$(update).pipe(
retry(3),
switchMap(({ points }) =>
of(
switchMap(({ points }) => {
const payloadWithClientProperties = {
...omit(update.payload, ['files']),
challengeFiles: update.payload.files ?? null
};
return of(
submitComplete({
username,
points,
...update.payload
...payloadWithClientProperties
}),
updateComplete()
)
),
);
}),
catchError(() => of(updateFailed(update)))
);
return saveChallenge;
@@ -141,7 +146,8 @@ export default function completionEpic(action$, state$) {
switchMap(({ type }) => {
const state = state$.value;
const meta = challengeMetaSelector(state);
const { nextChallengePath, challengeType, superBlock } = meta;
const { nextChallengePath, challengeType, superBlock, certification } =
meta;
const closeChallengeModal = of(closeModal('completion'));
let submitter = () => of({ type: 'no-user-signed-in' });
@@ -160,6 +166,7 @@ export default function completionEpic(action$, state$) {
const pathToNavigateTo = async () => {
return await findPathToNavigateTo(
certification,
nextChallengePath,
superBlock,
state,
@@ -177,6 +184,7 @@ export default function completionEpic(action$, state$) {
}
async function findPathToNavigateTo(
certification,
nextChallengePath,
superBlock,
state,
@@ -191,7 +199,7 @@ async function findPathToNavigateTo(
if (isProjectSubmission) {
const username = usernameSelector(state);
try {
const response = await getVerifyCanClaimCert(username, superBlock);
const response = await getVerifyCanClaimCert(username, certification);
if (response.status === 200) {
canClaimCert = response.data?.response?.message === 'can-claim-cert';
}

View File

@@ -120,8 +120,6 @@ export const resetChallenge = createAction(actionTypes.resetChallenge);
export const stopResetting = createAction(actionTypes.stopResetting);
export const submitChallenge = createAction(actionTypes.submitChallenge);
export const moveToTab = createAction(actionTypes.moveToTab);
export const setEditorFocusability = createAction(
actionTypes.setEditorFocusability
);
@@ -344,10 +342,6 @@ export const reducer = handleActions(
[payload]: true
}
}),
[actionTypes.moveToTab]: (state, { payload }) => ({
...state,
currentTab: payload
}),
[actionTypes.executeChallenge]: state => ({
...state,
currentTab: 3

View File

@@ -97,7 +97,9 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
const {
challengeMounted,
data: {
challengeNode: { title, challengeType, helpCategory }
challengeNode: {
challenge: { title, challengeType, helpCategory }
}
},
pageContext: { challengeMeta },
updateChallengeMeta
@@ -115,13 +117,17 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
componentDidUpdate(prevProps: ShowVideoProps): void {
const {
data: {
challengeNode: { title: prevTitle }
challengeNode: {
challenge: { title: prevTitle }
}
}
} = prevProps;
const {
challengeMounted,
data: {
challengeNode: { title: currentTitle, challengeType, helpCategory }
challengeNode: {
challenge: { title: currentTitle, challengeType, helpCategory }
}
},
pageContext: { challengeMeta },
updateChallengeMeta
@@ -169,16 +175,19 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
const {
data: {
challengeNode: {
fields: { blockName },
title,
description,
superBlock,
block,
translationPending,
videoId,
videoLocaleIds,
bilibiliIds,
question: { text, answers, solution }
challenge: {
fields: { blockName },
title,
description,
superBlock,
certification,
block,
translationPending,
videoId,
videoLocaleIds,
bilibiliIds,
question: { text, answers, solution }
}
}
},
openCompletionModal,
@@ -294,6 +303,7 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
<CompletionModal
block={block}
blockName={blockName}
certification={certification}
superBlock={superBlock}
/>
</Row>
@@ -313,34 +323,37 @@ export default connect(
export const query = graphql`
query VideoChallenge($slug: String!) {
challengeNode(fields: { slug: { eq: $slug } }) {
videoId
videoLocaleIds {
espanol
italian
portuguese
challengeNode(challenge: { fields: { slug: { eq: $slug } } }) {
challenge {
videoId
videoLocaleIds {
espanol
italian
portuguese
}
bilibiliIds {
aid
bvid
cid
}
title
description
challengeType
helpCategory
superBlock
certification
block
fields {
blockName
slug
}
question {
text
answers
solution
}
translationPending
}
bilibiliIds {
aid
bvid
cid
}
title
description
challengeType
helpCategory
superBlock
block
fields {
blockName
slug
}
question {
text
answers
solution
}
translationPending
}
}
`;

View File

@@ -101,7 +101,7 @@ export class Block extends Component<BlockProps> {
} = this.props;
let completedCount = 0;
const challengesWithCompleted = challenges.map(challenge => {
const challengesWithCompleted = challenges.map(({ challenge }) => {
const { id } = challenge;
const isCompleted = completedChallengeIds.some(
(completedChallengeId: string) => completedChallengeId === id
@@ -112,7 +112,7 @@ export class Block extends Component<BlockProps> {
return { ...challenge, isCompleted };
});
const isProjectBlock = challenges.some(challenge => {
const isProjectBlock = challenges.some(({ challenge }) => {
const isJsProject =
challenge.order === 10 && challenge.challengeType === 5;
@@ -136,12 +136,10 @@ export class Block extends Component<BlockProps> {
const blockIntroArr = blockIntroObj ? blockIntroObj.intro : [];
const {
expand: expandText,
collapse: collapseText,
courses: coursesText
collapse: collapseText
}: {
expand: string;
collapse: string;
courses: string;
} = t('intro:misc-text');
return isProjectBlock ? (
@@ -203,9 +201,7 @@ export class Block extends Component<BlockProps> {
>
<Caret />
<h4 className='course-title'>
{`${
isExpanded ? collapseText : expandText
} ${coursesText.toLowerCase()}`}
{`${isExpanded ? collapseText : expandText}`}
</h4>
<div className='map-title-completed course-title'>
{this.renderCheckMark(

View File

@@ -23,6 +23,8 @@ import { getVerifyCanClaimCert } from '../../../utils/ajax';
import CertificationCard from './certification-card';
interface CertChallengeProps {
// TODO: create enum/reuse SuperBlocks enum somehow
certification: string;
createFlashMessage: typeof createFlashMessage;
fetchState: {
pending: boolean;
@@ -63,6 +65,7 @@ const mapDispatchToProps = {
};
const CertChallenge = ({
certification,
createFlashMessage,
steps = {},
superBlock,
@@ -90,13 +93,23 @@ const CertChallenge = ({
if (username) {
void (async () => {
try {
const data = await getVerifyCanClaimCert(username, superBlock);
const { status, result } = data?.response?.message;
setCanClaimCert(status);
setCertVerificationMessage(result);
setVerificationComplete(true);
const data = await getVerifyCanClaimCert(username, certification);
if (data?.message) {
setCanClaimCert(false);
createFlashMessage(data.message);
} else {
const { status, result } = data?.response?.message;
setCanClaimCert(status);
setCertVerificationMessage(result);
}
} catch (e) {
// TODO: How do we handle errors...?
console.error(e);
createFlashMessage({
type: 'danger',
message: FlashMessages.ReallyWeird
});
} finally {
setVerificationComplete(true);
}
})();
}

View File

@@ -37,12 +37,10 @@ const CertificationCard = ({
const {
expand: expandText,
collapse: collapseText,
courses: coursesText
collapse: collapseText
}: {
expand: string;
collapse: string;
courses: string;
} = t('intro:misc-text');
return (
<ScrollableAnchor id='claim-cert-block'>
@@ -65,9 +63,7 @@ const CertificationCard = ({
>
<Caret />
<h4 className='course-title'>
{`${
isExpanded ? collapseText : expandText
} ${coursesText.toLowerCase()}`}
{`${isExpanded ? collapseText : expandText}`}
</h4>
<div className='map-title-completed course-title'>
{completedCount === numberOfSteps ? (

View File

@@ -21,7 +21,7 @@ function renderMenuItems({
edges?: Array<{ node: ChallengeNode }>;
}) {
return edges
.map(({ node }) => node)
.map(({ node: { challenge } }) => challenge)
.map(({ title, fields: { slug } }) => (
<Link key={'intro-' + slug} to={slug}>
<ListGroupItem>{title}</ListGroupItem>
@@ -42,7 +42,8 @@ function IntroductionPage({
html,
frontmatter: { block }
} = markdownRemark;
const firstLesson = allChallengeNode && allChallengeNode.edges[0].node;
const firstLesson =
allChallengeNode && allChallengeNode.edges[0].node.challenge;
const firstLessonPath = firstLesson
? firstLesson.fields.slug
: '/strange-place';
@@ -97,15 +98,23 @@ export const query = graphql`
html
}
allChallengeNode(
filter: { block: { eq: $block } }
sort: { fields: [superOrder, order, challengeOrder] }
filter: { challenge: { block: { eq: $block } } }
sort: {
fields: [
challenge___superOrder
challenge___order
challenge___challengeOrder
]
}
) {
edges {
node {
fields {
slug
challenge {
fields {
slug
}
title
}
title
}
}
}

View File

@@ -141,15 +141,15 @@ const SuperBlockIntroductionPage = (props: SuperBlockProp) => {
if (isSignedIn) {
// see if currentChallenge is in this superBlock
const currentChallengeEdge = edges.find(
edge => edge.node.id === currentChallengeId
edge => edge.node.challenge.id === currentChallengeId
);
return currentChallengeEdge
? currentChallengeEdge.node.block
: edge.node.block;
? currentChallengeEdge.node.challenge.block
: edge.node.challenge.block;
}
return edge.node.block;
return edge.node.challenge.block;
};
const initializeExpandedState = () => {
@@ -162,7 +162,7 @@ const SuperBlockIntroductionPage = (props: SuperBlockProp) => {
const {
data: {
markdownRemark: {
frontmatter: { superBlock, title }
frontmatter: { superBlock, title, certification }
},
allChallengeNode: { edges }
},
@@ -173,7 +173,10 @@ const SuperBlockIntroductionPage = (props: SuperBlockProp) => {
} = props;
const nodesForSuperBlock = edges.map(({ node }) => node);
const blockDashedNames = uniq(nodesForSuperBlock.map(({ block }) => block));
const blockDashedNames = uniq(
nodesForSuperBlock.map(({ challenge: { block } }) => block)
);
const i18nSuperBlock = t(`intro:${superBlock}.title`);
const i18nTitle =
superBlock === SuperBlocks.CodingInterviewPrep
@@ -182,6 +185,8 @@ const SuperBlockIntroductionPage = (props: SuperBlockProp) => {
cert: i18nSuperBlock
});
const defaultCurriculumNames = blockDashedNames;
return (
<>
<Helmet>
@@ -201,12 +206,12 @@ const SuperBlockIntroductionPage = (props: SuperBlockProp) => {
</h2>
<Spacer />
<div className='block-ui'>
{blockDashedNames.map(blockDashedName => (
{defaultCurriculumNames.map(blockDashedName => (
<Fragment key={blockDashedName}>
<Block
blockDashedName={blockDashedName}
challenges={nodesForSuperBlock.filter(
node => node.block === blockDashedName
node => node.challenge.block === blockDashedName
)}
superBlock={superBlock}
/>
@@ -216,6 +221,7 @@ const SuperBlockIntroductionPage = (props: SuperBlockProp) => {
{superBlock !== SuperBlocks.CodingInterviewPrep && (
<div>
<CertChallenge
certification={certification}
superBlock={superBlock}
title={title}
user={user}
@@ -258,27 +264,36 @@ export const query = graphql`
query SuperBlockIntroPageBySlug($slug: String!, $superBlock: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
frontmatter {
certification
superBlock
title
}
}
allChallengeNode(
sort: { fields: [superOrder, order, challengeOrder] }
filter: { superBlock: { eq: $superBlock } }
sort: {
fields: [
challenge___superOrder
challenge___order
challenge___challengeOrder
]
}
filter: { challenge: { superBlock: { eq: $superBlock } } }
) {
edges {
node {
fields {
slug
blockName
challenge {
fields {
slug
blockName
}
id
block
challengeType
title
order
superBlock
dashedName
}
id
block
challengeType
title
order
superBlock
dashedName
}
}
}

View File

@@ -1,5 +1,6 @@
import cookies from 'browser-cookies';
import envData from '../../../config/env.json';
import { FlashMessageArg } from '../components/Flash/redux';
import type {
ChallengeFile,
@@ -172,14 +173,15 @@ export interface GetVerifyCanClaimCert {
};
isCertMap: ClaimedCertifications;
completedChallenges: CompletedChallenge[];
message?: FlashMessageArg;
}
export function getVerifyCanClaimCert(
username: string,
superBlock: string
certification: string
): Promise<GetVerifyCanClaimCert> {
return get(
`/certificate/verify-can-claim-cert?username=${username}&superBlock=${superBlock}`
`/certificate/verify-can-claim-cert?username=${username}&superBlock=${certification}`
);
}

View File

@@ -21,12 +21,16 @@ const algoliaIndices = {
name: 'news-zh',
searchPage: 'https://chinese.freecodecamp.org/news/search'
},
// TODO: Replace with i18n pages when shipped
italian: {
name: 'news',
searchPage: 'https://www.freecodecamp.org/news/search/'
name: 'news-it',
searchPage: 'https://www.freecodecamp.org/italian/news/search/'
},
portuguese: {
name: 'news-pt',
searchPage: 'https://www.freecodecamp.org/portuguese/news/search/'
},
// TODO: Replace with Ukrainian news when we have more useful resources on that instance
ukrainian: {
name: 'news',
searchPage: 'https://www.freecodecamp.org/news/search/'
}

View File

@@ -36,6 +36,18 @@ exports.challengeTypes = {
codeally
};
// (Oliver) I don't think we need this for codeally projects, so they're ignored
// here
exports.isProject = challengeType => {
if (typeof challengeType !== 'number')
throw Error('challengeType must be a number');
return (
challengeType === frontEndProject ||
challengeType === backEndProject ||
challengeType === pythonProject
);
};
// turn challengeType to file ext
exports.pathsMap = {
[html]: 'html',

View File

@@ -45,12 +45,12 @@ const views = {
function getNextChallengePath(_node, index, nodeArray) {
const next = nodeArray[index + 1];
return next ? next.node.fields.slug : '/learn';
return next ? next.node.challenge.fields.slug : '/learn';
}
function getPrevChallengePath(_node, index, nodeArray) {
const prev = nodeArray[index - 1];
return prev ? prev.node.fields.slug : '/learn';
return prev ? prev.node.challenge.fields.slug : '/learn';
}
function getTemplateComponent(challengeType) {
@@ -58,8 +58,9 @@ function getTemplateComponent(challengeType) {
}
exports.createChallengePages = function (createPage) {
return function ({ node: challenge }, index, allChallengeEdges) {
return function ({ node: { challenge } }, index, allChallengeEdges) {
const {
certification,
superBlock,
block,
fields: { slug },
@@ -76,6 +77,7 @@ exports.createChallengePages = function (createPage) {
component: getTemplateComponent(challengeType),
context: {
challengeMeta: {
certification,
superBlock,
block,
template,
@@ -103,8 +105,8 @@ function getProjectPreviewConfig(challenge, allChallengeEdges) {
const { block, challengeOrder, usesMultifileEditor } = challenge;
const challengesInBlock = allChallengeEdges
.filter(({ node }) => node.block === block)
.map(({ node }) => node);
.filter(({ node: { challenge } }) => challenge.block === block)
.map(({ node: { challenge } }) => challenge);
const lastChallenge = challengesInBlock[challengesInBlock.length - 1];
const solutionToLastChallenge = sortChallengeFiles(
lastChallenge.solutions[0] ?? []
@@ -152,13 +154,23 @@ exports.createSuperBlockIntroPages = function (createPage) {
return function (edge) {
const {
fields: { slug },
frontmatter: { superBlock }
frontmatter: { superBlock, certification }
} = edge.node;
if (!certification) {
throw Error(
`superBlockIntro page, '${superBlock}' must have certification in frontmatter`
);
}
// TODO: throw if it encounters an unknown certification. Also, handle
// coding-interview-prep. it's not a certification, but it is a superBlock.
createPage({
path: slug,
component: superBlockIntro,
context: {
certification,
superBlock,
slug
}

View File

@@ -79,6 +79,7 @@
"learn-github-by-building-a-list-of-inspirational-quotes": "Relational Databases",
"number-guessing-game": "Relational Databases",
"learn-accessibility-by-building-a-quiz": "HTML-CSS",
"learn-css-colors-by-building-a-color-markers-set": "HTML-CSS",
"learn-html-forms-by-building-a-registration-form": "HTML-CSS",
"learn-css-animation-by-building-a-ferris-wheel": "HTML-CSS",
"learn-css-flexbox-by-building-a-photo-gallery": "HTML-CSS",
@@ -86,5 +87,9 @@
"learn-css-transforms-by-building-a-penguin": "HTML-CSS",
"learn-typography-by-building-a-nutrition-label": "HTML-CSS",
"learn-more-about-css-pseudo-selectors-by-building-a-balance-sheet": "HTML-CSS",
"learn-css-colors-by-building-a-color-markers-set": "HTML-CSS"
"build-a-personal-portfolio-webpage-project": "HTML-CSS",
"build-a-product-landing-page-project": "HTML-CSS",
"build-a-survey-form-project": "HTML-CSS",
"build-a-technical-documentation-page-project": "HTML-CSS",
"build-a-tribute-page-project": "HTML-CSS"
}

View File

@@ -113,7 +113,11 @@ export const superBlockCertTypeMap = {
[SuperBlocks.SciCompPy]: certTypes.sciCompPyV7,
[SuperBlocks.DataAnalysisPy]: certTypes.dataAnalysisPyV7,
[SuperBlocks.MachineLearningPy]: certTypes.machineLearningPyV7,
[SuperBlocks.RelationalDb]: certTypes.relationalDatabasesV8
[SuperBlocks.RelationalDb]: certTypes.relationalDatabasesV8,
// post-modern
// TODO: use enum
'responsive-web-design-22': certTypes.respWebDesign
};
export const certTypeIdMap = {

View File

@@ -15,7 +15,8 @@ export const availableLangs = {
'chinese',
'chinese-traditional',
'italian',
'portuguese'
'portuguese',
'ukrainian'
],
curriculum: [
'english',
@@ -23,7 +24,8 @@ export const availableLangs = {
'chinese',
'chinese-traditional',
'italian',
'portuguese'
'portuguese',
'ukrainian'
]
};
@@ -92,6 +94,20 @@ export const auditedCerts = {
SuperBlocks.MachineLearningPy,
SuperBlocks.CodingInterviewPrep,
SuperBlocks.RelationalDb
],
ukrainian: [
SuperBlocks.RespWebDesign,
SuperBlocks.JsAlgoDataStruct,
SuperBlocks.FrontEndDevLibs,
SuperBlocks.DataVis,
SuperBlocks.BackEndDevApis,
SuperBlocks.QualityAssurance,
SuperBlocks.SciCompPy,
SuperBlocks.DataAnalysisPy,
SuperBlocks.InfoSec,
SuperBlocks.MachineLearningPy,
SuperBlocks.CodingInterviewPrep,
SuperBlocks.RelationalDb
]
};
@@ -109,7 +125,8 @@ export const i18nextCodes = {
chinese: 'zh',
'chinese-traditional': 'zh-Hant',
italian: 'it',
portuguese: 'pt-BR'
portuguese: 'pt-BR',
ukrainian: 'uk'
};
// These are for the language selector dropdown menu in the footer
@@ -119,7 +136,8 @@ export const langDisplayNames = {
chinese: '中文(简体字)',
'chinese-traditional': '中文(繁體字)',
italian: 'Italiano',
portuguese: 'Português'
portuguese: 'Português',
ukrainian: 'Українська'
};
/* These are for formatting dates and numbers. Used with JS .toLocaleString().
@@ -132,5 +150,6 @@ export const langCodes = {
chinese: 'zh',
'chinese-traditional': 'zh-Hant',
italian: 'it',
portuguese: 'pt-BR'
portuguese: 'pt-BR',
ukrainian: 'uk'
};

View File

@@ -7,7 +7,6 @@
"template": "",
"required": [],
"superBlock": "quality-assurance",
"superOrder": 6,
"challengeOrder": [
[
"5895f700f9fc0f352b528e63",

View File

@@ -7,7 +7,6 @@
"template": "",
"required": [],
"superBlock": "coding-interview-prep",
"superOrder": 11,
"challengeOrder": [
[
"a3f503de51cf954ede28891d",
@@ -44,5 +43,9 @@
[
"587d825c367417b2b2512c8f",
"Implement Merge Sort"
],
[
"61abc7ebf3029b56226de5b6",
"Implement Binary Search"
]
]}

View File

@@ -7,7 +7,6 @@
"template": "",
"required": [],
"superBlock": "responsive-web-design",
"superOrder": 0,
"challengeOrder": [
[
"587d774c367417b2b2512a9c",

View File

@@ -7,7 +7,6 @@
"template": "",
"required": [],
"superBlock": "responsive-web-design",
"superOrder": 0,
"challengeOrder": [
[
"587d7791367417b2b2512ab3",

View File

@@ -5,7 +5,6 @@
"order": 3,
"time": "150 hours",
"superBlock": "back-end-development-and-apis",
"superOrder": 5,
"challengeOrder": [
[
"bd7158d8c443edefaeb5bdef",

View File

@@ -7,7 +7,6 @@
"template": "",
"required": [],
"superBlock": "javascript-algorithms-and-data-structures",
"superOrder": 1,
"challengeOrder": [
[
"56533eb9ac21ba0edf2244b3",

View File

@@ -7,7 +7,6 @@
"template": "",
"required": [],
"superBlock": "responsive-web-design",
"superOrder": 0,
"challengeOrder": [
[
"bad87fee1348bd9aedf08803",

View File

@@ -7,7 +7,6 @@
"template": "",
"required": [],
"superBlock": "javascript-algorithms-and-data-structures",
"superOrder": 1,
"challengeOrder": [
[
"587d7b7e367417b2b2512b20",

View File

@@ -7,7 +7,6 @@
"template": "",
"required": [],
"superBlock": "responsive-web-design",
"superOrder": 0,
"challengeOrder": [
[
"bd7123c8c441eddfaeb5bdef",

View File

@@ -2,13 +2,13 @@
"name": "Basic JavaScript RPG Game",
"isUpcomingChange": true,
"usesMultifileEditor": true,
"hasEditableBoundaries": true,
"dashedName": "basic-javascript-rpg-game",
"order": 10,
"time": "2 hours",
"template": "",
"required": [],
"superBlock": "javascript-algorithms-and-data-structures",
"superOrder": 1,
"isBeta": true,
"challengeOrder": [
[

View File

@@ -7,7 +7,6 @@
"template": "",
"required": [],
"superBlock": "javascript-algorithms-and-data-structures",
"superOrder": 1,
"challengeOrder": [
[
"bd7123c9c441eddfaeb4bdef",

View File

@@ -5,7 +5,6 @@
"order": 1,
"time": "5 hours",
"superBlock": "back-end-development-and-apis",
"superOrder": 5,
"challengeOrder": [
[
"587d7fb0367417b2b2512bed",

View File

@@ -11,7 +11,6 @@
}
],
"superBlock": "front-end-development-libraries",
"superOrder": 2,
"challengeOrder": [
[
"bad87fee1348bd9acde08712",

View File

@@ -0,0 +1,14 @@
{
"name": "Build a Personal Portfolio Webpage Project",
"isUpcomingChange": false,
"order": 19,
"time": "30 hours",
"template": "",
"required": [],
"superBlock": "responsive-web-design",
"challengeOrder": [
[
"bd7158d8c242eddfaeb5bd13",
"Build a Personal Portfolio Webpage"
]
]}

View File

@@ -0,0 +1,14 @@
{
"name": "Build a Product Landing Page Project",
"isUpcomingChange": false,
"order": 16,
"time": "30 hours",
"template": "",
"required": [],
"superBlock": "responsive-web-design",
"challengeOrder": [
[
"587d78af367417b2b2512b04",
"Build a Product Landing Page"
]
]}

View File

@@ -0,0 +1,14 @@
{
"name": "Build a Survey Form Project",
"isUpcomingChange": false,
"order": 4,
"time": "30 hours",
"template": "",
"required": [],
"superBlock": "responsive-web-design",
"challengeOrder": [
[
"587d78af367417b2b2512b03",
"Build a Survey Form"
]
]}

View File

@@ -0,0 +1,14 @@
{
"name": "Build a Technical Documentation Page Project",
"isUpcomingChange": false,
"order": 13,
"time": "30 hours",
"template": "",
"required": [],
"superBlock": "responsive-web-design",
"challengeOrder": [
[
"587d78b0367417b2b2512b05",
"Build a Technical Documentation Page"
]
]}

View File

@@ -0,0 +1,14 @@
{
"name": "Build a Tribute Page Project",
"isUpcomingChange": false,
"order": 9,
"time": "30 hours",
"template": "",
"required": [],
"superBlock": "responsive-web-design",
"challengeOrder": [
[
"bd7158d8c442eddfaeb5bd18",
"Build a Tribute Page"
]
]}

View File

@@ -7,7 +7,6 @@
"template": "",
"required": [],
"superBlock": "responsive-web-design",
"superOrder": 0,
"challengeOrder": [
[
"587d78ab367417b2b2512af0",

View File

@@ -7,7 +7,6 @@
"template": "",
"required": [],
"superBlock": "responsive-web-design",
"superOrder": 0,
"challengeOrder": [
[
"5a858944d96184f06fd60d61",

View File

@@ -2,6 +2,7 @@
"name": "D3 Dashboard",
"isUpcomingChange": true,
"usesMultifileEditor": true,
"hasEditableBoundaries": true,
"dashedName": "d3-dashboard",
"order": 3,
"time": "5 hours",
@@ -12,7 +13,6 @@
}
],
"superBlock": "data-visualization",
"superOrder": 3,
"challengeOrder": [
[
"5d8a4cfbe6b6180ed9a1c9de",

View File

@@ -7,7 +7,6 @@
"template": "",
"required": [],
"superBlock": "data-analysis-with-python",
"superOrder": 8,
"challengeOrder": [
[
"5e9a093a74c4063ca6f7c14c",

View File

@@ -5,7 +5,6 @@
"order": 2,
"time": "150 hours",
"superBlock": "data-analysis-with-python",
"superOrder": 8,
"challengeOrder": [
[
"5e46f7e5ac417301a38fb928",

Some files were not shown because too many files have changed in this diff Show More