mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-03-01 08:05:25 -05:00
feat(client/curriculum): add upcoming english superblock and challenge types (#52201)
This commit is contained in:
@@ -36,6 +36,7 @@ const foundationalCSharpBase =
|
||||
'/learn/foundational-c-sharp-with-microsoft/foundational-c-sharp-with-microsoft-certification-exam';
|
||||
const upcomingPythonBase = '/learn/upcoming-python';
|
||||
const exampleCertBase = '/learn/example-certification';
|
||||
const a2EnglishBase = '/learn/a2-english-for-developers';
|
||||
const legacyFrontEndBase = feLibsBase;
|
||||
const legacyFrontEndResponsiveBase = responsiveWebBase;
|
||||
const legacyFrontEndTakeHomeBase = takeHomeBase;
|
||||
@@ -786,6 +787,19 @@ const allStandardCerts = [
|
||||
certSlug: Certification.UpcomingPython
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '651dd7e01d697d0aab7833b7',
|
||||
title: 'A2 English for Developers',
|
||||
certSlug: Certification.A2English,
|
||||
projects: [
|
||||
{
|
||||
id: '651dd3e06ffb500e3f2ce478',
|
||||
title: 'Challenge 1',
|
||||
link: `${a2EnglishBase}/learn-greetings-in-your-first-day-at-the-office/challenge-1`,
|
||||
certSlug: Certification.A2English
|
||||
}
|
||||
]
|
||||
}
|
||||
] as const;
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ exports.createPages = function createPages({ graphql, actions, reporter }) {
|
||||
edges {
|
||||
node {
|
||||
challenge {
|
||||
audioPath
|
||||
block
|
||||
certification
|
||||
challengeType
|
||||
@@ -84,6 +85,13 @@ exports.createPages = function createPages({ graphql, actions, reporter }) {
|
||||
slug
|
||||
blockHashSlug
|
||||
}
|
||||
fillInTheBlank {
|
||||
sentence
|
||||
blanks {
|
||||
answer
|
||||
feedback
|
||||
}
|
||||
}
|
||||
hasEditableBoundaries
|
||||
id
|
||||
msTrophyId
|
||||
@@ -287,12 +295,14 @@ exports.createSchemaCustomization = ({ actions }) => {
|
||||
challenge: Challenge
|
||||
}
|
||||
type Challenge {
|
||||
audioPath: String
|
||||
challengeFiles: [FileContents]
|
||||
notes: String
|
||||
url: String
|
||||
assignments: [String]
|
||||
prerequisites: [PrerequisiteChallenge]
|
||||
msTrophyId: String
|
||||
fillInTheBlank: FillInTheBlank
|
||||
}
|
||||
type FileContents {
|
||||
fileKey: String
|
||||
@@ -307,6 +317,14 @@ exports.createSchemaCustomization = ({ actions }) => {
|
||||
id: String
|
||||
title: String
|
||||
}
|
||||
type FillInTheBlank {
|
||||
sentence: String
|
||||
blanks: [Blank]
|
||||
}
|
||||
type Blank {
|
||||
answer: String
|
||||
feedback: String
|
||||
}
|
||||
`;
|
||||
createTypes(typeDefs);
|
||||
};
|
||||
|
||||
@@ -1146,6 +1146,27 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"a2-english-for-developers": {
|
||||
"title": "A2 English for Developers (Beta)",
|
||||
"intro": [
|
||||
"This course teaches the English language, designed for developers.",
|
||||
"More text here."
|
||||
],
|
||||
"blocks": {
|
||||
"learn-greetings-in-your-first-day-at-the-office": {
|
||||
"title": "Learn Greetings in your First Day at the Office",
|
||||
"intro": ["Learn greetings."]
|
||||
},
|
||||
"learn-introductions-in-an-online-team-meeting": {
|
||||
"title": "Learn Introductions in an Online Team Meeting",
|
||||
"intro": ["Learn introductions."]
|
||||
},
|
||||
"learn-conversation-starters-in-the-break-room": {
|
||||
"title": "Learn Conversation Starters in the Break Room",
|
||||
"intro": ["Learn conversation starters."]
|
||||
}
|
||||
}
|
||||
},
|
||||
"example-certification": {
|
||||
"title": "Example Certification",
|
||||
"intro": ["placeholder"],
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"settings": "Settings",
|
||||
"take-me": "Take me to the Challenges",
|
||||
"check-answer": "Check your answer",
|
||||
"submit": "Submit",
|
||||
"get-hint": "Get a Hint",
|
||||
"ask-for-help": "Ask for Help",
|
||||
"create-post": "Create a help post on the forum",
|
||||
@@ -417,6 +418,7 @@
|
||||
"building-a-university": "We're Building a Free Computer Science University Degree Program",
|
||||
"if-help-university": "We've already made a ton of progress. Support our charity with the long road ahead.",
|
||||
"preview-external-window": "Preview currently showing in external window.",
|
||||
"fill-in-the-blank": "Fill in the blank",
|
||||
"exam": {
|
||||
"qualified": "Congratulations, you have completed all the requirements to qualify for the exam.",
|
||||
"not-qualified": "You have not met the requirements to be eligible for the exam. To qualify, please complete the following challenges:",
|
||||
@@ -890,6 +892,8 @@
|
||||
"college-algebra-with-python-v8": "College Algebra with Python Certification",
|
||||
"Foundational C# with Microsoft": "Foundational C# with Microsoft",
|
||||
"foundational-c-sharp-with-microsoft": "Foundational C# with Microsoft Certification",
|
||||
"A2 English for Developers": "A2 English for Developers",
|
||||
"a2-english-for-developers": "A2 English for Developers Certification",
|
||||
"Legacy Front End": "Legacy Front End",
|
||||
"legacy-front-end": "Front End Certification",
|
||||
"Legacy Back End": "Legacy Back End",
|
||||
|
||||
@@ -37,7 +37,8 @@ const iconMap = {
|
||||
[SuperBlocks.CollegeAlgebraPy]: CollegeAlgebra,
|
||||
[SuperBlocks.FoundationalCSharp]: CSharpLogo,
|
||||
[SuperBlocks.ExampleCertification]: ResponsiveDesign,
|
||||
[SuperBlocks.UpcomingPython]: PythonIcon
|
||||
[SuperBlocks.UpcomingPython]: PythonIcon,
|
||||
[SuperBlocks.A2English]: Graduation
|
||||
};
|
||||
|
||||
type SuperBlockIconProps = {
|
||||
|
||||
@@ -131,7 +131,8 @@ const isCertMapSelector = createSelector(
|
||||
// TODO: remove Example Certification? Also, include Upcoming Python
|
||||
// Certification.
|
||||
'Example Certification': false,
|
||||
'Upcoming Python Certification': false
|
||||
'Upcoming Python Certification': false,
|
||||
'A2 English for Developers': false
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: A2 English for Developers
|
||||
superBlock: a2-english-for-developers
|
||||
certification: a2-english-for-developers
|
||||
---
|
||||
|
||||
## Introduction to A2 English for Developers
|
||||
|
||||
A2 English for Developers
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Learn Conversation Starters in the Break Room
|
||||
block: learn-conversation-starters-in-the-break-room
|
||||
superBlock: a2-english-for-developers
|
||||
---
|
||||
|
||||
## Introduction to Learn Conversation Starters in the Break Room
|
||||
|
||||
Learn Conversation Starters in the Break Room
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Learn Greetings in your First Day at the Office
|
||||
block: learn-greetings-in-your-first-day-at-the-office
|
||||
superBlock: a2-english-for-developers
|
||||
---
|
||||
|
||||
## Introduction to Learn Greetings in your First Day at the Office
|
||||
|
||||
Learn Greetings in your First Day at the Office
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Learn Introductions in an Online Team Meeting
|
||||
block: learn-introductions-in-an-online-team-meeting
|
||||
superBlock: a2-english-for-developers
|
||||
---
|
||||
|
||||
## Introduction to Learn Introductions in an Online Team Meeting
|
||||
|
||||
Learn Introductions in an Online Team Meeting
|
||||
@@ -57,6 +57,12 @@ export type Question = {
|
||||
answers: MultipleChoiceAnswer[];
|
||||
solution: number;
|
||||
};
|
||||
|
||||
export type FillInTheBlank = {
|
||||
sentence: string;
|
||||
blanks: MultipleChoiceAnswer[];
|
||||
};
|
||||
|
||||
export type Fields = {
|
||||
slug: string;
|
||||
blockHashSlug: string;
|
||||
@@ -102,6 +108,7 @@ export type ChallengeWithCompletedNode = {
|
||||
|
||||
export type ChallengeNode = {
|
||||
challenge: {
|
||||
audioPath: string;
|
||||
block: string;
|
||||
certification: string;
|
||||
challengeOrder: number;
|
||||
@@ -110,6 +117,7 @@ export type ChallengeNode = {
|
||||
description: string;
|
||||
challengeFiles: ChallengeFiles;
|
||||
fields: Fields;
|
||||
fillInTheBlank: FillInTheBlank;
|
||||
forumTopicId: number;
|
||||
guideUrl: string;
|
||||
head: string[];
|
||||
|
||||
334
client/src/templates/Challenges/dialogue/show.tsx
Normal file
334
client/src/templates/Challenges/dialogue/show.tsx
Normal file
@@ -0,0 +1,334 @@
|
||||
// Package Utilities
|
||||
import { Button } from '@freecodecamp/react-bootstrap';
|
||||
import { graphql } from 'gatsby';
|
||||
import React, { Component } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { ObserveKeys } from 'react-hotkeys';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import type { Dispatch } from 'redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Container, Col, Row } from '@freecodecamp/ui';
|
||||
|
||||
// Local Utilities
|
||||
import Loader from '../../../components/helpers/loader';
|
||||
import Spacer from '../../../components/helpers/spacer';
|
||||
import LearnLayout from '../../../components/layouts/learn';
|
||||
import { ChallengeNode, ChallengeMeta } from '../../../redux/prop-types';
|
||||
import Hotkeys from '../components/hotkeys';
|
||||
import VideoPlayer from '../components/video-player';
|
||||
import CompletionModal from '../components/completion-modal';
|
||||
import HelpModal from '../components/help-modal';
|
||||
import PrismFormatted from '../components/prism-formatted';
|
||||
import {
|
||||
challengeMounted,
|
||||
updateChallengeMeta,
|
||||
openModal
|
||||
} from '../redux/actions';
|
||||
import { isChallengeCompletedSelector } from '../redux/selectors';
|
||||
|
||||
// Styles
|
||||
import '../odin/show.css';
|
||||
import '../video.css';
|
||||
|
||||
// Redux Setup
|
||||
const mapStateToProps = createSelector(
|
||||
isChallengeCompletedSelector,
|
||||
(isChallengeCompleted: boolean) => ({
|
||||
isChallengeCompleted
|
||||
})
|
||||
);
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch) =>
|
||||
bindActionCreators(
|
||||
{
|
||||
updateChallengeMeta,
|
||||
challengeMounted,
|
||||
openCompletionModal: () => openModal('completion'),
|
||||
openHelpModal: () => openModal('help')
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
// Types
|
||||
interface ShowDialogueProps {
|
||||
challengeMounted: (arg0: string) => void;
|
||||
data: { challengeNode: ChallengeNode };
|
||||
description: string;
|
||||
isChallengeCompleted: boolean;
|
||||
openCompletionModal: () => void;
|
||||
openHelpModal: () => void;
|
||||
pageContext: {
|
||||
challengeMeta: ChallengeMeta;
|
||||
};
|
||||
t: TFunction;
|
||||
updateChallengeMeta: (arg0: ChallengeMeta) => void;
|
||||
}
|
||||
|
||||
interface ShowDialogueState {
|
||||
subtitles: string;
|
||||
downloadURL: string | null;
|
||||
assignmentsCompleted: number;
|
||||
allAssignmentsCompleted: boolean;
|
||||
videoIsLoaded: boolean;
|
||||
}
|
||||
|
||||
// Component
|
||||
class ShowDialogue extends Component<ShowDialogueProps, ShowDialogueState> {
|
||||
static displayName: string;
|
||||
private container: React.RefObject<HTMLElement> = React.createRef();
|
||||
|
||||
constructor(props: ShowDialogueProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
subtitles: '',
|
||||
downloadURL: null,
|
||||
assignmentsCompleted: 0,
|
||||
allAssignmentsCompleted: false,
|
||||
videoIsLoaded: false
|
||||
};
|
||||
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
const {
|
||||
challengeMounted,
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: { title, challengeType, helpCategory }
|
||||
}
|
||||
},
|
||||
pageContext: { challengeMeta },
|
||||
updateChallengeMeta
|
||||
} = this.props;
|
||||
updateChallengeMeta({
|
||||
...challengeMeta,
|
||||
title,
|
||||
challengeType,
|
||||
helpCategory
|
||||
});
|
||||
challengeMounted(challengeMeta.id);
|
||||
this.container.current?.focus();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: ShowDialogueProps): void {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: { title: prevTitle }
|
||||
}
|
||||
}
|
||||
} = prevProps;
|
||||
const {
|
||||
challengeMounted,
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: { title: currentTitle, challengeType, helpCategory }
|
||||
}
|
||||
},
|
||||
pageContext: { challengeMeta },
|
||||
updateChallengeMeta
|
||||
} = this.props;
|
||||
if (prevTitle !== currentTitle) {
|
||||
updateChallengeMeta({
|
||||
...challengeMeta,
|
||||
title: currentTitle,
|
||||
challengeType,
|
||||
helpCategory
|
||||
});
|
||||
challengeMounted(challengeMeta.id);
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit() {
|
||||
const { openCompletionModal } = this.props;
|
||||
if (this.state.allAssignmentsCompleted) {
|
||||
openCompletionModal();
|
||||
}
|
||||
}
|
||||
|
||||
handleAssignmentChange = (
|
||||
event: React.ChangeEvent<HTMLInputElement>,
|
||||
totalAssignments: number
|
||||
): void => {
|
||||
const assignmentsCompleted = event.target.checked
|
||||
? this.state.assignmentsCompleted + 1
|
||||
: this.state.assignmentsCompleted - 1;
|
||||
const allAssignmentsCompleted = totalAssignments === assignmentsCompleted;
|
||||
|
||||
this.setState({
|
||||
assignmentsCompleted,
|
||||
allAssignmentsCompleted
|
||||
});
|
||||
};
|
||||
|
||||
onVideoLoad = () => {
|
||||
this.setState({
|
||||
videoIsLoaded: true
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: {
|
||||
title,
|
||||
description,
|
||||
superBlock,
|
||||
block,
|
||||
videoId,
|
||||
fields: { blockName },
|
||||
assignments
|
||||
}
|
||||
}
|
||||
},
|
||||
openHelpModal,
|
||||
pageContext: {
|
||||
challengeMeta: { nextChallengePath, prevChallengePath }
|
||||
},
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
const blockNameTitle = `${t(
|
||||
`intro:${superBlock}.blocks.${block}.title`
|
||||
)} - ${title}`;
|
||||
|
||||
return (
|
||||
<Hotkeys
|
||||
executeChallenge={() => this.handleSubmit()}
|
||||
containerRef={this.container}
|
||||
nextChallengePath={nextChallengePath}
|
||||
prevChallengePath={prevChallengePath}
|
||||
>
|
||||
<LearnLayout>
|
||||
<Helmet
|
||||
title={`${blockNameTitle} | ${t('learn.learn')} | freeCodeCamp.org`}
|
||||
/>
|
||||
<Container>
|
||||
<Row>
|
||||
{videoId && (
|
||||
<Col lg={10} lgOffset={1} md={10} mdOffset={1}>
|
||||
<Spacer size='medium' />
|
||||
<div className='video-wrapper'>
|
||||
{!this.state.videoIsLoaded ? (
|
||||
<div className='video-placeholder-loader'>
|
||||
<Loader />
|
||||
</div>
|
||||
) : null}
|
||||
<VideoPlayer
|
||||
onVideoLoad={this.onVideoLoad}
|
||||
title={title}
|
||||
videoId={videoId}
|
||||
videoIsLoaded={this.state.videoIsLoaded}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
)}
|
||||
<Col md={8} mdOffset={2} sm={10} smOffset={1} xs={12}>
|
||||
<Spacer size='medium' />
|
||||
<h2>{title}</h2>
|
||||
<PrismFormatted className={'line-numbers'} text={description} />
|
||||
<Spacer size='medium' />
|
||||
<ObserveKeys>
|
||||
<h2>{t('learn.assignments')}</h2>
|
||||
<div className='video-quiz-options'>
|
||||
{assignments.map((assignment, index) => (
|
||||
<label className='video-quiz-option-label' key={index}>
|
||||
<input
|
||||
name='assignment'
|
||||
type='checkbox'
|
||||
onChange={event =>
|
||||
this.handleAssignmentChange(
|
||||
event,
|
||||
assignments.length
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<PrismFormatted
|
||||
className={'video-quiz-option'}
|
||||
text={assignment}
|
||||
/>
|
||||
<Spacer size='medium' />
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
<Spacer size='medium' />
|
||||
</ObserveKeys>
|
||||
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center'
|
||||
}}
|
||||
>
|
||||
{!this.state.allAssignmentsCompleted &&
|
||||
assignments.length > 0 && (
|
||||
<>
|
||||
<br />
|
||||
<span>{t('learn.assignment-not-complete')}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Spacer size='medium' />
|
||||
<Button
|
||||
block={true}
|
||||
bsSize='large'
|
||||
bsStyle='primary'
|
||||
disabled={!this.state.allAssignmentsCompleted}
|
||||
onClick={() => this.handleSubmit()}
|
||||
>
|
||||
{t('buttons.submit')}
|
||||
</Button>
|
||||
<Button
|
||||
block={true}
|
||||
bsSize='large'
|
||||
bsStyle='primary'
|
||||
className='btn-invert'
|
||||
onClick={openHelpModal}
|
||||
>
|
||||
{t('buttons.ask-for-help')}
|
||||
</Button>
|
||||
<Spacer size='large' />
|
||||
</Col>
|
||||
<CompletionModal />
|
||||
<HelpModal challengeTitle={title} challengeBlock={blockName} />
|
||||
</Row>
|
||||
</Container>
|
||||
</LearnLayout>
|
||||
</Hotkeys>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ShowDialogue.displayName = 'ShowDialogue';
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(withTranslation()(ShowDialogue));
|
||||
|
||||
export const query = graphql`
|
||||
query Dialogue($slug: String!) {
|
||||
challengeNode(challenge: { fields: { slug: { eq: $slug } } }) {
|
||||
challenge {
|
||||
videoId
|
||||
title
|
||||
description
|
||||
challengeType
|
||||
helpCategory
|
||||
superBlock
|
||||
block
|
||||
fields {
|
||||
slug
|
||||
blockName
|
||||
}
|
||||
translationPending
|
||||
assignments
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
44
client/src/templates/Challenges/fill-in-the-blank/show.css
Normal file
44
client/src/templates/Challenges/fill-in-the-blank/show.css
Normal file
@@ -0,0 +1,44 @@
|
||||
.fill-in-the-blank-input {
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
background-color: var(--quaternary-background);
|
||||
color: var(--tertiary-color);
|
||||
border-radius: 0;
|
||||
font-family: var(--font-family-monospace);
|
||||
overflow-wrap: anywhere;
|
||||
line-height: 1.5rem;
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
border: 1px solid var(--secondary-color);
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-bottom-width: 4px;
|
||||
border-bottom-color: var(--gray-45) !important;
|
||||
}
|
||||
|
||||
.code-tag code {
|
||||
font-size: 100%;
|
||||
z-index: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.first-code-tag code {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.middle-code-tag code {
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.last-code-tag code {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.green-underline {
|
||||
border-bottom-color: var(--success-background) !important;
|
||||
}
|
||||
|
||||
.red-underline {
|
||||
border-bottom-color: var(--danger-background) !important;
|
||||
}
|
||||
436
client/src/templates/Challenges/fill-in-the-blank/show.tsx
Normal file
436
client/src/templates/Challenges/fill-in-the-blank/show.tsx
Normal file
@@ -0,0 +1,436 @@
|
||||
// Package Utilities
|
||||
import { Button } from '@freecodecamp/react-bootstrap';
|
||||
import { graphql } from 'gatsby';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { ObserveKeys } from 'react-hotkeys';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import type { Dispatch } from 'redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Container, Col, Row } from '@freecodecamp/ui';
|
||||
|
||||
// Local Utilities
|
||||
import Spacer from '../../../components/helpers/spacer';
|
||||
import LearnLayout from '../../../components/layouts/learn';
|
||||
import { ChallengeNode, ChallengeMeta } from '../../../redux/prop-types';
|
||||
import Hotkeys from '../components/hotkeys';
|
||||
import ChallengeTitle from '../components/challenge-title';
|
||||
import CompletionModal from '../components/completion-modal';
|
||||
import HelpModal from '../components/help-modal';
|
||||
import PrismFormatted from '../components/prism-formatted';
|
||||
import {
|
||||
challengeMounted,
|
||||
updateChallengeMeta,
|
||||
openModal,
|
||||
updateSolutionFormValues
|
||||
} from '../redux/actions';
|
||||
import { isChallengeCompletedSelector } from '../redux/selectors';
|
||||
|
||||
// Styles
|
||||
import '../video.css';
|
||||
import './show.css';
|
||||
|
||||
// Redux Setup
|
||||
const mapStateToProps = createSelector(
|
||||
isChallengeCompletedSelector,
|
||||
(isChallengeCompleted: boolean) => ({
|
||||
isChallengeCompleted
|
||||
})
|
||||
);
|
||||
const mapDispatchToProps = (dispatch: Dispatch) =>
|
||||
bindActionCreators(
|
||||
{
|
||||
updateChallengeMeta,
|
||||
challengeMounted,
|
||||
updateSolutionFormValues,
|
||||
openCompletionModal: () => openModal('completion'),
|
||||
openHelpModal: () => openModal('help')
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
// Types
|
||||
interface ShowFillInTheBlankProps {
|
||||
challengeMounted: (arg0: string) => void;
|
||||
data: { challengeNode: ChallengeNode };
|
||||
description: string;
|
||||
isChallengeCompleted: boolean;
|
||||
openCompletionModal: () => void;
|
||||
openHelpModal: () => void;
|
||||
pageContext: {
|
||||
challengeMeta: ChallengeMeta;
|
||||
};
|
||||
t: TFunction;
|
||||
updateChallengeMeta: (arg0: ChallengeMeta) => void;
|
||||
updateSolutionFormValues: () => void;
|
||||
}
|
||||
|
||||
interface ShowFillInTheBlankState {
|
||||
showWrong: boolean;
|
||||
userAnswers: (string | null)[];
|
||||
answersCorrect: (boolean | null)[];
|
||||
allBlanksFilled: boolean;
|
||||
feedback: string | null;
|
||||
showFeedback: boolean;
|
||||
}
|
||||
|
||||
// Component
|
||||
class ShowFillInTheBlank extends Component<
|
||||
ShowFillInTheBlankProps,
|
||||
ShowFillInTheBlankState
|
||||
> {
|
||||
static displayName: string;
|
||||
private container: React.RefObject<HTMLElement> = React.createRef();
|
||||
|
||||
constructor(props: ShowFillInTheBlankProps) {
|
||||
super(props);
|
||||
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: {
|
||||
fillInTheBlank: { blanks }
|
||||
}
|
||||
}
|
||||
}
|
||||
} = props;
|
||||
|
||||
const emptyArray = blanks.map(() => null);
|
||||
|
||||
this.state = {
|
||||
showWrong: false,
|
||||
userAnswers: emptyArray,
|
||||
answersCorrect: emptyArray,
|
||||
allBlanksFilled: false,
|
||||
feedback: null,
|
||||
showFeedback: false
|
||||
};
|
||||
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
const {
|
||||
challengeMounted,
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: { title, challengeType, helpCategory }
|
||||
}
|
||||
},
|
||||
pageContext: { challengeMeta },
|
||||
updateChallengeMeta
|
||||
} = this.props;
|
||||
updateChallengeMeta({
|
||||
...challengeMeta,
|
||||
title,
|
||||
challengeType,
|
||||
helpCategory
|
||||
});
|
||||
challengeMounted(challengeMeta.id);
|
||||
this.container.current?.focus();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: ShowFillInTheBlankProps): void {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: { title: prevTitle }
|
||||
}
|
||||
}
|
||||
} = prevProps;
|
||||
const {
|
||||
challengeMounted,
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: { title: currentTitle, challengeType, helpCategory }
|
||||
}
|
||||
},
|
||||
pageContext: { challengeMeta },
|
||||
updateChallengeMeta
|
||||
} = this.props;
|
||||
if (prevTitle !== currentTitle) {
|
||||
updateChallengeMeta({
|
||||
...challengeMeta,
|
||||
title: currentTitle,
|
||||
challengeType,
|
||||
helpCategory
|
||||
});
|
||||
challengeMounted(challengeMeta.id);
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit() {
|
||||
const {
|
||||
openCompletionModal,
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: {
|
||||
fillInTheBlank: { blanks }
|
||||
}
|
||||
}
|
||||
}
|
||||
} = this.props;
|
||||
const { userAnswers } = this.state;
|
||||
|
||||
const blankAnswers = blanks.map(b => b.answer);
|
||||
|
||||
const newAnswersCorrect = userAnswers.map(
|
||||
(userAnswer, i) => userAnswer === blankAnswers[i]
|
||||
);
|
||||
|
||||
const hasWrongAnswer = newAnswersCorrect.some(a => a === false);
|
||||
if (!hasWrongAnswer) {
|
||||
this.setState({
|
||||
answersCorrect: newAnswersCorrect
|
||||
});
|
||||
|
||||
openCompletionModal();
|
||||
} else {
|
||||
const firstWrongIndex = newAnswersCorrect.findIndex(a => a === false);
|
||||
const feedback =
|
||||
firstWrongIndex >= 0 ? blanks[firstWrongIndex].feedback : null;
|
||||
|
||||
this.setState({
|
||||
answersCorrect: newAnswersCorrect,
|
||||
showWrong: true,
|
||||
showFeedback: true,
|
||||
feedback: feedback
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleInputChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
const { userAnswers, answersCorrect } = this.state;
|
||||
const inputIndex = parseInt(e.target.getAttribute('data-index') as string);
|
||||
|
||||
const newUserAnswers = [...userAnswers];
|
||||
newUserAnswers[inputIndex] = e.target.value;
|
||||
|
||||
const newAnswersCorrect = [...answersCorrect];
|
||||
newAnswersCorrect[inputIndex] = null;
|
||||
|
||||
const allBlanksFilled = newUserAnswers.every(a => a);
|
||||
|
||||
this.setState({
|
||||
userAnswers: newUserAnswers,
|
||||
answersCorrect: newAnswersCorrect,
|
||||
allBlanksFilled,
|
||||
showWrong: false
|
||||
});
|
||||
};
|
||||
|
||||
addCodeTags(str: string, index: number, numberOfBlanks: number): string {
|
||||
if (index === 0) return `${str}</code>`;
|
||||
if (index < numberOfBlanks) return `<code>${str}</code>`;
|
||||
return `<code>${str}`;
|
||||
}
|
||||
|
||||
addPrismClass(index: number, numberOfBlanks: number): string {
|
||||
if (index === 0) return `first-code-tag`;
|
||||
if (index < numberOfBlanks) return `middle-code-tag`;
|
||||
return `last-code-tag`;
|
||||
}
|
||||
|
||||
addInputClass(index: number): string {
|
||||
const { answersCorrect } = this.state;
|
||||
if (answersCorrect[index] === true) return 'green-underline';
|
||||
if (answersCorrect[index] === false) return 'red-underline';
|
||||
return '';
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
data: {
|
||||
challengeNode: {
|
||||
challenge: {
|
||||
title,
|
||||
description,
|
||||
instructions,
|
||||
superBlock,
|
||||
block,
|
||||
translationPending,
|
||||
fields: { blockName },
|
||||
fillInTheBlank: { sentence, blanks },
|
||||
audioPath
|
||||
}
|
||||
}
|
||||
},
|
||||
openHelpModal,
|
||||
pageContext: {
|
||||
challengeMeta: { nextChallengePath, prevChallengePath }
|
||||
},
|
||||
t,
|
||||
isChallengeCompleted
|
||||
} = this.props;
|
||||
|
||||
const blockNameTitle = `${t(
|
||||
`intro:${superBlock}.blocks.${block}.title`
|
||||
)} - ${title}`;
|
||||
|
||||
const { allBlanksFilled, feedback, showFeedback, showWrong } = this.state;
|
||||
|
||||
const splitSentence = sentence.replace(/^<p>|<\/p>$/g, '').split('_');
|
||||
const blankAnswers = blanks.map(b => b.answer);
|
||||
|
||||
return (
|
||||
<Hotkeys
|
||||
executeChallenge={() => this.handleSubmit()}
|
||||
containerRef={this.container}
|
||||
nextChallengePath={nextChallengePath}
|
||||
prevChallengePath={prevChallengePath}
|
||||
>
|
||||
<LearnLayout>
|
||||
<Helmet
|
||||
title={`${blockNameTitle} | ${t('learn.learn')} | freeCodeCamp.org`}
|
||||
/>
|
||||
<Container>
|
||||
<Row>
|
||||
<Spacer size='medium' />
|
||||
<ChallengeTitle
|
||||
isCompleted={isChallengeCompleted}
|
||||
translationPending={translationPending}
|
||||
>
|
||||
{title}
|
||||
</ChallengeTitle>
|
||||
|
||||
<Col md={8} mdOffset={2} sm={10} smOffset={1} xs={12}>
|
||||
<PrismFormatted text={description} />
|
||||
{audioPath && (
|
||||
<>
|
||||
<Spacer size='small' />
|
||||
<Spacer size='small' />
|
||||
{/* TODO: Add tracks for audio elements */}
|
||||
{/* eslint-disable-next-line jsx-a11y/media-has-caption*/}
|
||||
<audio className='audio' controls>
|
||||
<source
|
||||
src={`https://cdn.freecodecamp.org/${audioPath}`}
|
||||
type='audio/mp3'
|
||||
></source>
|
||||
</audio>
|
||||
</>
|
||||
)}
|
||||
<Spacer size='medium' />
|
||||
<PrismFormatted text={instructions} />
|
||||
<Spacer size='medium' />
|
||||
<h2>{t('learn.fill-in-the-blank')}</h2>
|
||||
<Spacer size='small' />
|
||||
<ObserveKeys>
|
||||
<div>
|
||||
<p>
|
||||
{splitSentence.map((s, i) => {
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
<PrismFormatted
|
||||
text={this.addCodeTags(s, i, blankAnswers.length)}
|
||||
className={`code-tag ${this.addPrismClass(
|
||||
i,
|
||||
blankAnswers.length
|
||||
)}`}
|
||||
useSpan
|
||||
noAria
|
||||
/>
|
||||
{blankAnswers[i] && (
|
||||
<input
|
||||
type='text'
|
||||
maxLength={blankAnswers[i].length + 3}
|
||||
className={`fill-in-the-blank-input ${this.addInputClass(
|
||||
i
|
||||
)}`}
|
||||
onChange={this.handleInputChange}
|
||||
data-index={i}
|
||||
style={{
|
||||
width: `${blankAnswers[i].length * 11 + 11}px`
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</ObserveKeys>
|
||||
<Spacer size='medium' />
|
||||
{showFeedback && feedback && (
|
||||
<>
|
||||
<PrismFormatted text={feedback} />
|
||||
<Spacer size='small' />
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center'
|
||||
}}
|
||||
>
|
||||
{showWrong ? (
|
||||
<span>{t('learn.wrong-answer')}</span>
|
||||
) : (
|
||||
<span>{t('learn.check-answer')}</span>
|
||||
)}
|
||||
</div>
|
||||
<Spacer size='medium' />
|
||||
<Button
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
disabled={!allBlanksFilled}
|
||||
onClick={() => this.handleSubmit()}
|
||||
>
|
||||
{t('buttons.check-answer')}
|
||||
</Button>
|
||||
<Button
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
className='btn-invert'
|
||||
onClick={openHelpModal}
|
||||
>
|
||||
{t('buttons.ask-for-help')}
|
||||
</Button>
|
||||
<Spacer size='large' />
|
||||
</Col>
|
||||
<CompletionModal />
|
||||
<HelpModal challengeTitle={title} challengeBlock={blockName} />
|
||||
</Row>
|
||||
</Container>
|
||||
</LearnLayout>
|
||||
</Hotkeys>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ShowFillInTheBlank.displayName = 'ShowFillInTheBlank';
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(withTranslation()(ShowFillInTheBlank));
|
||||
|
||||
export const query = graphql`
|
||||
query FillInTheBlankChallenge($slug: String!) {
|
||||
challengeNode(challenge: { fields: { slug: { eq: $slug } } }) {
|
||||
challenge {
|
||||
title
|
||||
description
|
||||
instructions
|
||||
challengeType
|
||||
helpCategory
|
||||
superBlock
|
||||
block
|
||||
fields {
|
||||
blockName
|
||||
slug
|
||||
}
|
||||
fillInTheBlank {
|
||||
sentence
|
||||
blanks {
|
||||
answer
|
||||
feedback
|
||||
}
|
||||
}
|
||||
translationPending
|
||||
audioPath
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -26,3 +26,7 @@ input[type='checkbox']::before {
|
||||
input[type='checkbox']:checked::before {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
audio {
|
||||
background-color: aqua;
|
||||
}
|
||||
|
||||
@@ -216,7 +216,8 @@ class ShowOdin extends Component<ShowOdinProps, ShowOdinState> {
|
||||
bilibiliIds,
|
||||
fields: { blockName },
|
||||
question: { text, answers, solution },
|
||||
assignments
|
||||
assignments,
|
||||
audioPath
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -276,7 +277,21 @@ class ShowOdin extends Component<ShowOdinProps, ShowOdinState> {
|
||||
<Spacer size='medium' />
|
||||
<h2>{title}</h2>
|
||||
<PrismFormatted className={'line-numbers'} text={description} />
|
||||
<Spacer size='medium' />
|
||||
{audioPath && (
|
||||
<>
|
||||
<Spacer size='small' />
|
||||
<Spacer size='small' />
|
||||
{/* TODO: Add tracks for audio elements */}
|
||||
{/* eslint-disable-next-line jsx-a11y/media-has-caption*/}
|
||||
<audio className='audio' controls>
|
||||
<source
|
||||
src={`https://cdn.freecodecamp.org/${audioPath}`}
|
||||
type='audio/mp3'
|
||||
></source>
|
||||
</audio>
|
||||
<Spacer size='medium' />
|
||||
</>
|
||||
)}
|
||||
<ObserveKeys>
|
||||
{assignments.length > 0 && (
|
||||
<>
|
||||
@@ -443,6 +458,7 @@ export const query = graphql`
|
||||
}
|
||||
translationPending
|
||||
assignments
|
||||
audioPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,16 @@ const msTrophy = path.resolve(
|
||||
'../../src/templates/Challenges/ms-trophy/show.tsx'
|
||||
);
|
||||
|
||||
const dialogue = path.resolve(
|
||||
__dirname,
|
||||
'../../src/templates/Challenges/dialogue/show.tsx'
|
||||
);
|
||||
|
||||
const fillInTheBlank = path.resolve(
|
||||
__dirname,
|
||||
'../../src/templates/Challenges/fill-in-the-blank/show.tsx'
|
||||
);
|
||||
|
||||
const views = {
|
||||
backend,
|
||||
classic,
|
||||
@@ -58,7 +68,9 @@ const views = {
|
||||
codeAlly,
|
||||
odin,
|
||||
exam,
|
||||
msTrophy
|
||||
msTrophy,
|
||||
dialogue,
|
||||
fillInTheBlank
|
||||
// quiz: Quiz
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "Learn Conversation Starters in the Break Room",
|
||||
"isUpcomingChange": true,
|
||||
"dashedName": "learn-conversation-starters-in-the-break-room",
|
||||
"order": 2,
|
||||
"time": "5 hours",
|
||||
"template": "",
|
||||
"required": [],
|
||||
"superBlock": "a2-english-for-developers",
|
||||
"isBeta": true,
|
||||
"challengeOrder": [
|
||||
{
|
||||
"id": "651dd5ae6ffb500e3f2ce47c",
|
||||
"title": "Challenge 1"
|
||||
},
|
||||
{
|
||||
"id": "651dd5d86ffb500e3f2ce47d",
|
||||
"title": "Challenge 2"
|
||||
},
|
||||
{
|
||||
"id": "651dd5f41d697d0aab7833b5",
|
||||
"title": "Challenge 3"
|
||||
}
|
||||
],
|
||||
"helpCategory": "HTML-CSS"
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "Learn Greetings in your First Day at the Office",
|
||||
"isUpcomingChange": true,
|
||||
"dashedName": "learn-greetings-in-your-first-day-at-the-office",
|
||||
"order": 0,
|
||||
"time": "5 hours",
|
||||
"template": "",
|
||||
"required": [],
|
||||
"superBlock": "a2-english-for-developers",
|
||||
"isBeta": true,
|
||||
"challengeOrder": [
|
||||
{
|
||||
"id": "651dd3e06ffb500e3f2ce478",
|
||||
"title": "Dialogue: Introducing"
|
||||
},
|
||||
{
|
||||
"id": "651dd5296ffb500e3f2ce479",
|
||||
"title": "You Are"
|
||||
},
|
||||
{
|
||||
"id": "651dd5386ffb500e3f2ce47a",
|
||||
"title": "Right"
|
||||
},
|
||||
{
|
||||
"id": "6537e6ece93e5724eeb27c54",
|
||||
"title": "Name and Job Title"
|
||||
},
|
||||
{
|
||||
"id": "6543aa3df5f028dba112f275",
|
||||
"title": "Team Lead"
|
||||
},
|
||||
{
|
||||
"id": "6543aaa9f5f028dba112f276",
|
||||
"title": "That's Right"
|
||||
},
|
||||
{
|
||||
"id": "6543aae6f5f028dba112f277",
|
||||
"title": "That's Right: 2"
|
||||
},
|
||||
{
|
||||
"id": "6543abeff5f028dba112f278",
|
||||
"title": "I am: I'm"
|
||||
},
|
||||
{
|
||||
"id": "6543abf5f5f028dba112f279",
|
||||
"title": "I'm"
|
||||
}
|
||||
],
|
||||
"helpCategory": "HTML-CSS"
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "Learn Introductions in an Online Team Meeting",
|
||||
"isUpcomingChange": true,
|
||||
"dashedName": "learn-introductions-in-an-online-team-meeting",
|
||||
"order": 1,
|
||||
"time": "5 hours",
|
||||
"template": "",
|
||||
"required": [],
|
||||
"superBlock": "a2-english-for-developers",
|
||||
"isBeta": true,
|
||||
"challengeOrder": [
|
||||
{
|
||||
"id": "651dd5a46ffb500e3f2ce47b",
|
||||
"title": "Challenge 1"
|
||||
},
|
||||
{
|
||||
"id": "651dd5e46ffb500e3f2ce47e",
|
||||
"title": "Challenge 2"
|
||||
},
|
||||
{
|
||||
"id": "651dd6071d697d0aab7833b6",
|
||||
"title": "Challenge 3"
|
||||
}
|
||||
],
|
||||
"helpCategory": "HTML-CSS"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
id: 651dd7e01d697d0aab7833b7
|
||||
title: A2 English for Developers Certification
|
||||
certification: a2-english-for-developers
|
||||
challengeType: 7
|
||||
isPrivate: true
|
||||
tests:
|
||||
- id: 651dd3e06ffb500e3f2ce478
|
||||
title: Challenge 1
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
id: 651dd5ae6ffb500e3f2ce47c
|
||||
title: Challenge 1
|
||||
challengeType: 11
|
||||
videoId: nLDychdBwUg
|
||||
dashedName: challenge-1
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Here's the description
|
||||
|
||||
# --instructions--
|
||||
|
||||
Here's the instructions
|
||||
|
||||
# --question--
|
||||
|
||||
## --text--
|
||||
|
||||
What is Maria doing when she says, `"You must be the new graphic designer"`?
|
||||
|
||||
## --answers--
|
||||
|
||||
Asking about someone's job role.
|
||||
|
||||
### --feedback--
|
||||
|
||||
No, that's not correct
|
||||
|
||||
---
|
||||
|
||||
Giving a job description.
|
||||
|
||||
### --feedback--
|
||||
|
||||
No, that's not correct
|
||||
|
||||
```js
|
||||
console.log('with code');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Making a statement based on her assumption.
|
||||
|
||||
### --feedback--
|
||||
|
||||
No, that's not correct
|
||||
|
||||
---
|
||||
|
||||
Expressing a possibility.
|
||||
|
||||
### --feedback--
|
||||
|
||||
No, that's not correct
|
||||
|
||||
---
|
||||
|
||||
Giving a job description.
|
||||
|
||||
### --feedback--
|
||||
|
||||
No, that's not correct
|
||||
|
||||
---
|
||||
|
||||
Giving a job description.
|
||||
|
||||
### --feedback--
|
||||
|
||||
No, that's not correct
|
||||
|
||||
---
|
||||
|
||||
Giving a job description.
|
||||
|
||||
### --feedback--
|
||||
|
||||
No, that's not correct
|
||||
|
||||
---
|
||||
|
||||
Giving a job description.
|
||||
|
||||
### --feedback--
|
||||
|
||||
No, that's not correct
|
||||
|
||||
## --video-solution--
|
||||
|
||||
3
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
id: 651dd5d86ffb500e3f2ce47d
|
||||
title: Challenge 2
|
||||
challengeType: 11
|
||||
videoId: nLDychdBwUg
|
||||
dashedName: challenge-2
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Here's the description
|
||||
|
||||
# --instructions--
|
||||
|
||||
Here's the instructions
|
||||
|
||||
# --question--
|
||||
|
||||
## --text--
|
||||
|
||||
What is Maria assuming about Tom?
|
||||
|
||||
## --answers--
|
||||
|
||||
Tom is the team lead.
|
||||
|
||||
---
|
||||
|
||||
Maria is the new graphic designer.
|
||||
|
||||
---
|
||||
|
||||
Tom is leaving the company.
|
||||
|
||||
---
|
||||
|
||||
Tom is the new graphic designer.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
4
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
id: 651dd5f41d697d0aab7833b5
|
||||
title: Challenge 3
|
||||
challengeType: 11
|
||||
videoId: nLDychdBwUg
|
||||
dashedName: challenge-3
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Here's the description
|
||||
|
||||
# --instructions--
|
||||
|
||||
Here's the instructions
|
||||
Fill in the blank.
|
||||
|
||||
Hello! You ____ __ the new graphic designer. I'm Maria, the team lead.
|
||||
|
||||
# --question--
|
||||
|
||||
## --text--
|
||||
|
||||
What is Maria assuming about Tom?
|
||||
|
||||
## --answers--
|
||||
|
||||
Tom is the team lead.
|
||||
|
||||
---
|
||||
|
||||
Maria is the new graphic designer.
|
||||
|
||||
---
|
||||
|
||||
Tom is leaving the company.
|
||||
|
||||
---
|
||||
|
||||
Tom is the new graphic designer.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
4
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
id: 651dd3e06ffb500e3f2ce478
|
||||
title: "Dialogue: Introducing"
|
||||
challengeType: 21
|
||||
videoId: nLDychdBwUg
|
||||
dashedName: dialogue-introducing
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
What the video above to understand the context of the upcoming lessons.
|
||||
|
||||
# --assignment--
|
||||
|
||||
Watch the video
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
id: 6543abeff5f028dba112f278
|
||||
title: "I am: I'm"
|
||||
challengeType: 19
|
||||
dashedName: i-am-im
|
||||
audioPath: curriculum/js-music-player/We-Are-Going-to-Make-it.mp3
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In English, the verb `to be` is used to talk about identities, characteristics, and more. The contraction `I'm` is a combination of `I` and `am`. Here, Tom uses it to introduce himself.
|
||||
|
||||
# --question--
|
||||
|
||||
## --text--
|
||||
|
||||
Which operation correctly expands the contraction `I'm`?
|
||||
|
||||
## --answers--
|
||||
|
||||
`I is`
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about which verb form would correctly fit with `I` to talk about oneself in the present.
|
||||
|
||||
---
|
||||
|
||||
`I am`
|
||||
|
||||
---
|
||||
|
||||
`I was`
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about which verb form would correctly fit with `I` to talk about oneself in the present.
|
||||
|
||||
---
|
||||
|
||||
`I have`
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about which verb form would correctly fit with `I` to talk about oneself in the present.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
2
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
id: 6543abf5f5f028dba112f279
|
||||
title: "I'm"
|
||||
challengeType: 22
|
||||
dashedName: im
|
||||
audioPath: curriculum/js-music-player/We-Are-Going-to-Make-it.mp3
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
The word `I'm` is a contraction of `I am`. Contractions are a way to shorten common combinations of words, especially with verbs.
|
||||
|
||||
# --fillInTheBlank--
|
||||
|
||||
## --sentence--
|
||||
|
||||
`Hi, that's right! _ Tom McKenzie.`
|
||||
|
||||
## --blanks--
|
||||
|
||||
`I'm`
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
id: 6537e6ece93e5724eeb27c54
|
||||
title: Name and Job Title
|
||||
challengeType: 19
|
||||
dashedName: name-and-job-title
|
||||
audioPath: curriculum/js-music-player/We-Are-Going-to-Make-it.mp3
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In English, we often mention our job or role in a company by saying, `I'm [Name], the [Job title].` This lets others know our position or role.
|
||||
|
||||
# --question--
|
||||
|
||||
## --text--
|
||||
|
||||
What is Maria's job role at the company?
|
||||
|
||||
## --answers--
|
||||
|
||||
`Graphic Designer`
|
||||
|
||||
### --feedback--
|
||||
|
||||
Focus on the term Maria used to describe herself.
|
||||
|
||||
---
|
||||
|
||||
`Team Member`
|
||||
|
||||
### --feedback--
|
||||
|
||||
Focus on the term Maria used to describe herself.
|
||||
|
||||
---
|
||||
|
||||
`Team Lead`
|
||||
|
||||
### --feedback--
|
||||
|
||||
Focus on the term Maria used to describe herself.
|
||||
|
||||
---
|
||||
|
||||
`CEO`
|
||||
|
||||
### --feedback--
|
||||
|
||||
Focus on the term Maria used to describe herself.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
3
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
id: 651dd5386ffb500e3f2ce47a
|
||||
title: Right
|
||||
challengeType: 22
|
||||
dashedName: right
|
||||
audioPath: curriculum/js-music-player/We-Are-Going-to-Make-it.mp3
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In English, to check or confirm something people sometimes use tag questions. For example, `You are a programmer, right?` Here, `right?` is used as a tag to check or confirm the previous statement.
|
||||
|
||||
# --fillInTheBlank--
|
||||
|
||||
## --sentence--
|
||||
|
||||
`Hello, You _ the new graphic designer, _?`
|
||||
|
||||
## --blanks--
|
||||
|
||||
`are`
|
||||
|
||||
### --feedback--
|
||||
|
||||
Pay attention to the verb in the sentence.
|
||||
|
||||
---
|
||||
|
||||
`right`
|
||||
|
||||
### --feedback--
|
||||
|
||||
Pay attention to the verb in the sentence.
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
id: 6543aa3df5f028dba112f275
|
||||
title: Team Lead
|
||||
challengeType: 22
|
||||
dashedName: team-lead
|
||||
audioPath: curriculum/js-music-player/We-Are-Going-to-Make-it.mp3
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
A `team lead` is a person who leads or manages a team. In the dialogue, Maria introduces herself as the team lead, meaning she has a leadership role.
|
||||
|
||||
# --fillInTheBlank--
|
||||
|
||||
## --sentence--
|
||||
|
||||
`I'm Maria, the _ _.`
|
||||
|
||||
## --blanks--
|
||||
|
||||
team
|
||||
|
||||
### --feedback--
|
||||
|
||||
Focus on the term Maria used to describe herself.
|
||||
|
||||
---
|
||||
|
||||
lead
|
||||
|
||||
### --feedback--
|
||||
|
||||
Focus on the term Maria used to describe herself.
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
id: 6543aae6f5f028dba112f277
|
||||
title: "That's Right: 2"
|
||||
challengeType: 22
|
||||
dashedName: thats-right-2
|
||||
audioPath: curriculum/js-music-player/We-Are-Going-to-Make-it.mp3
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Placeholder Description ___ __
|
||||
|
||||
# --fillInTheBlank--
|
||||
|
||||
## --sentence--
|
||||
|
||||
`Hi, _ _! I'm Tom McKenzie. It's a pleasure to meet you.`
|
||||
|
||||
## --blanks--
|
||||
|
||||
`that's`
|
||||
|
||||
---
|
||||
|
||||
`right`
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
id: 6543aaa9f5f028dba112f276
|
||||
title: "That's Right"
|
||||
challengeType: 19
|
||||
dashedName: thats-right
|
||||
audioPath: curriculum/js-music-player/We-Are-Going-to-Make-it.mp3
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
When someone makes a correct assumption or guess about you, you can confirm it using phrases like `that's right`. It's a way of agreeing or saying yes to what is said.
|
||||
|
||||
# --question--
|
||||
|
||||
## --text--
|
||||
|
||||
Which phrase does Tom use to confirm Maria's statement about him?
|
||||
|
||||
## --answers--
|
||||
|
||||
`that's wrong`
|
||||
|
||||
### --feedback--
|
||||
|
||||
`That's wrong` is used to disagree.
|
||||
|
||||
---
|
||||
|
||||
`that's okay`
|
||||
|
||||
### --feedback--
|
||||
|
||||
`that's okay` usually shows acceptance, not confirmation.
|
||||
|
||||
---
|
||||
|
||||
`that's right`
|
||||
|
||||
---
|
||||
|
||||
`that's left`
|
||||
|
||||
### --feedback--
|
||||
|
||||
`that's left` refers to a direction, not confirmation.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
3
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
id: 651dd5296ffb500e3f2ce479
|
||||
title: You Are
|
||||
challengeType: 22
|
||||
dashedName: you-are
|
||||
audioPath: curriculum/js-music-player/We-Are-Going-to-Make-it.mp3
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In English, when making introductions or identifying someone, you use the verb `to be`. In this case, `You are` is used to address the person Maria is talking to and affirmatively identify their occupation.
|
||||
|
||||
Maria is introducing herself and confirming Tom's job role. `Are` is used in the present affirmative to make a statement.
|
||||
|
||||
# --fillInTheBlank--
|
||||
|
||||
## --sentence--
|
||||
|
||||
`Hello, You _ the new graphic designer, right?`
|
||||
|
||||
## --blanks--
|
||||
|
||||
are
|
||||
|
||||
### --feedback--
|
||||
|
||||
The verb `to be` is an irregular verb. When conjugated with the pronoun `you`, `be` becomes `are`. For example: `You are an English learner.`
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
id: 651dd5a46ffb500e3f2ce47b
|
||||
title: Challenge 1
|
||||
challengeType: 11
|
||||
videoId: nLDychdBwUg
|
||||
dashedName: challenge-1
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Here's the description
|
||||
|
||||
# --instructions--
|
||||
|
||||
Here's the instructions
|
||||
|
||||
# --question--
|
||||
|
||||
## --text--
|
||||
|
||||
What is Maria doing when she says, `"You must be the new graphic designer"`?
|
||||
|
||||
## --answers--
|
||||
|
||||
Asking about someone's job role.
|
||||
|
||||
---
|
||||
|
||||
Giving a job description.
|
||||
|
||||
---
|
||||
|
||||
Making a statement based on her assumption.
|
||||
|
||||
---
|
||||
|
||||
Expressing a possibility.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
3
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
id: 651dd5e46ffb500e3f2ce47e
|
||||
title: Challenge 2
|
||||
challengeType: 11
|
||||
videoId: nLDychdBwUg
|
||||
dashedName: challenge-2
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Here's the description
|
||||
|
||||
# --instructions--
|
||||
|
||||
Here's the instructions
|
||||
|
||||
# --question--
|
||||
|
||||
## --text--
|
||||
|
||||
What is Maria assuming about Tom?
|
||||
|
||||
## --answers--
|
||||
|
||||
Tom is the team lead.
|
||||
|
||||
---
|
||||
|
||||
Maria is the new graphic designer.
|
||||
|
||||
---
|
||||
|
||||
Tom is leaving the company.
|
||||
|
||||
---
|
||||
|
||||
Tom is the new graphic designer.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
4
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
id: 651dd6071d697d0aab7833b6
|
||||
title: Challenge 3
|
||||
challengeType: 11
|
||||
videoId: nLDychdBwUg
|
||||
dashedName: challenge-3
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Here's the description
|
||||
|
||||
# --instructions--
|
||||
|
||||
Here's the instructions
|
||||
Fill in the blank.
|
||||
|
||||
Hello! You ____ __ the new graphic designer. I'm Maria, the team lead.
|
||||
|
||||
# --question--
|
||||
|
||||
## --text--
|
||||
|
||||
What is Maria assuming about Tom?
|
||||
|
||||
## --answers--
|
||||
|
||||
Tom is the team lead.
|
||||
|
||||
---
|
||||
|
||||
Maria is the new graphic designer.
|
||||
|
||||
---
|
||||
|
||||
Tom is leaving the company.
|
||||
|
||||
---
|
||||
|
||||
Tom is the new graphic designer.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
4
|
||||
@@ -28,17 +28,22 @@ const prerequisitesJoi = Joi.object().keys({
|
||||
|
||||
const schema = Joi.object()
|
||||
.keys({
|
||||
audioPath: Joi.string(),
|
||||
block: Joi.string().regex(slugRE).required(),
|
||||
blockId: Joi.objectId(),
|
||||
challengeOrder: Joi.number(),
|
||||
removeComments: Joi.bool().required(),
|
||||
certification: Joi.string().regex(slugRE),
|
||||
challengeType: Joi.number().min(0).max(20).required(),
|
||||
challengeType: Joi.number().min(0).max(22).required(),
|
||||
checksum: Joi.number(),
|
||||
// TODO: require this only for normal challenges, not certs
|
||||
dashedName: Joi.string().regex(slugRE),
|
||||
description: Joi.when('challengeType', {
|
||||
is: [challengeTypes.step, challengeTypes.video],
|
||||
is: [
|
||||
challengeTypes.step,
|
||||
challengeTypes.video,
|
||||
challengeTypes.fillInTheBlank
|
||||
],
|
||||
then: Joi.string().allow(''),
|
||||
otherwise: Joi.string().required()
|
||||
}),
|
||||
@@ -55,6 +60,17 @@ const schema = Joi.object()
|
||||
'C-Sharp'
|
||||
),
|
||||
videoUrl: Joi.string().allow(''),
|
||||
fillInTheBlank: Joi.object().keys({
|
||||
sentence: Joi.string().required(),
|
||||
blanks: Joi.array()
|
||||
.items(
|
||||
Joi.object().keys({
|
||||
answer: Joi.string().required(),
|
||||
feedback: Joi.string().allow(null)
|
||||
})
|
||||
)
|
||||
.required()
|
||||
}),
|
||||
forumTopicId: Joi.number(),
|
||||
id: Joi.objectId().required(),
|
||||
instructions: Joi.string().allow(''),
|
||||
@@ -73,7 +89,7 @@ const schema = Joi.object()
|
||||
}),
|
||||
// video challenges only:
|
||||
videoId: Joi.when('challengeType', {
|
||||
is: challengeTypes.video,
|
||||
is: [challengeTypes.video, challengeTypes.dialogue],
|
||||
then: Joi.string().required()
|
||||
}),
|
||||
videoLocaleIds: Joi.when('challengeType', {
|
||||
@@ -112,7 +128,11 @@ const schema = Joi.object()
|
||||
crossDomain: Joi.bool()
|
||||
})
|
||||
),
|
||||
assignments: Joi.array().items(Joi.string()),
|
||||
assignments: Joi.when('challengeType', {
|
||||
is: challengeTypes.dialogue,
|
||||
then: Joi.array().items(Joi.string()).required(),
|
||||
otherwise: Joi.array().items(Joi.string())
|
||||
}),
|
||||
solutions: Joi.array().items(Joi.array().items(fileJoi).min(1)),
|
||||
superBlock: Joi.string().regex(slugWithSlashRE),
|
||||
superOrder: Joi.number(),
|
||||
|
||||
@@ -82,6 +82,7 @@ const directoryToSuperblock = {
|
||||
'19-foundational-c-sharp-with-microsoft':
|
||||
'foundational-c-sharp-with-microsoft',
|
||||
'20-upcoming-python': 'upcoming-python',
|
||||
'21-a2-english-for-developers': 'a2-english-for-developers',
|
||||
'99-example-certification': 'example-certification'
|
||||
};
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ describe('getSuperBlockFromPath', () => {
|
||||
);
|
||||
|
||||
it('handles all the directories in ./challenges/english', () => {
|
||||
expect.assertions(21);
|
||||
expect.assertions(22);
|
||||
|
||||
for (const directory of directories) {
|
||||
expect(() => getSuperBlockFromDir(directory)).not.toThrow();
|
||||
@@ -150,7 +150,7 @@ describe('getSuperBlockFromPath', () => {
|
||||
});
|
||||
|
||||
it("returns valid superblocks (or 'certifications') for all valid arguments", () => {
|
||||
expect.assertions(21);
|
||||
expect.assertions(22);
|
||||
|
||||
const superBlockPaths = directories.filter(x => x !== '00-certifications');
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ export enum Certification {
|
||||
FoundationalCSharp = 'foundational-c-sharp-with-microsoft',
|
||||
// Upcoming certifications
|
||||
UpcomingPython = 'upcoming-python-v8',
|
||||
A2English = 'a2-english-for-developers-v8',
|
||||
// Legacy certifications
|
||||
LegacyFrontEnd = 'legacy-front-end',
|
||||
LegacyBackEnd = 'legacy-back-end',
|
||||
@@ -70,7 +71,10 @@ export const legacyFullStackCertification = [
|
||||
|
||||
// "Upcoming" certifications are standard certifications that are not live unless
|
||||
// showUpcomingChanges is true.
|
||||
export const upcomingCertifications = [Certification.UpcomingPython] as const;
|
||||
export const upcomingCertifications = [
|
||||
Certification.UpcomingPython,
|
||||
Certification.A2English
|
||||
] as const;
|
||||
|
||||
export const certTypes = {
|
||||
frontEnd: 'isFrontEndCert',
|
||||
|
||||
@@ -20,6 +20,8 @@ const exam = 17;
|
||||
const msTrophy = 18;
|
||||
const multipleChoice = 19;
|
||||
const python = 20;
|
||||
const dialogue = 21;
|
||||
const fillInTheBlank = 22;
|
||||
|
||||
export const challengeTypes = {
|
||||
html,
|
||||
@@ -43,7 +45,9 @@ export const challengeTypes = {
|
||||
exam,
|
||||
msTrophy,
|
||||
multipleChoice,
|
||||
python
|
||||
python,
|
||||
dialogue,
|
||||
fillInTheBlank
|
||||
};
|
||||
|
||||
export const isFinalProject = (challengeType: number) => {
|
||||
@@ -71,7 +75,9 @@ export const isCodeAllyPractice = (challengeType: number) => {
|
||||
export const hasNoTests = (challengeType: number): boolean =>
|
||||
challengeType === multipleChoice ||
|
||||
challengeType === theOdinProject ||
|
||||
challengeType === video;
|
||||
challengeType === video ||
|
||||
challengeType === dialogue ||
|
||||
challengeType === fillInTheBlank;
|
||||
|
||||
// determine the component view for each challenge
|
||||
export const viewTypes = {
|
||||
@@ -94,7 +100,9 @@ export const viewTypes = {
|
||||
[exam]: 'exam',
|
||||
[msTrophy]: 'msTrophy',
|
||||
[multipleChoice]: 'odin',
|
||||
[python]: 'modern'
|
||||
[python]: 'modern',
|
||||
[dialogue]: 'dialogue',
|
||||
[fillInTheBlank]: 'fillInTheBlank'
|
||||
};
|
||||
|
||||
// determine the type of submit function to use for the challenge on completion
|
||||
@@ -122,5 +130,7 @@ export const submitTypes = {
|
||||
[exam]: 'exam',
|
||||
[msTrophy]: 'msTrophy',
|
||||
[multipleChoice]: 'tests',
|
||||
[python]: 'tests'
|
||||
[python]: 'tests',
|
||||
[dialogue]: 'tests',
|
||||
[fillInTheBlank]: 'tests'
|
||||
};
|
||||
|
||||
@@ -21,7 +21,8 @@ export enum SuperBlocks {
|
||||
CollegeAlgebraPy = 'college-algebra-with-python',
|
||||
FoundationalCSharp = 'foundational-c-sharp-with-microsoft',
|
||||
ExampleCertification = 'example-certification',
|
||||
UpcomingPython = 'upcoming-python'
|
||||
UpcomingPython = 'upcoming-python',
|
||||
A2English = 'a2-english-for-developers'
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -78,7 +79,8 @@ export const superBlockOrder: SuperBlockOrder = {
|
||||
SuperBlocks.JsAlgoDataStructNew,
|
||||
SuperBlocks.TheOdinProject,
|
||||
SuperBlocks.ExampleCertification,
|
||||
SuperBlocks.UpcomingPython
|
||||
SuperBlocks.UpcomingPython,
|
||||
SuperBlocks.A2English
|
||||
]
|
||||
};
|
||||
|
||||
@@ -101,7 +103,8 @@ export const notAuditedSuperBlocks: NotAuditedSuperBlocks = {
|
||||
SuperBlocks.ProjectEuler,
|
||||
SuperBlocks.JsAlgoDataStructNew,
|
||||
SuperBlocks.TheOdinProject,
|
||||
SuperBlocks.UpcomingPython
|
||||
SuperBlocks.UpcomingPython,
|
||||
SuperBlocks.A2English
|
||||
],
|
||||
[Languages.Chinese]: [
|
||||
SuperBlocks.FoundationalCSharp,
|
||||
@@ -109,7 +112,8 @@ export const notAuditedSuperBlocks: NotAuditedSuperBlocks = {
|
||||
SuperBlocks.ProjectEuler,
|
||||
SuperBlocks.JsAlgoDataStructNew,
|
||||
SuperBlocks.TheOdinProject,
|
||||
SuperBlocks.UpcomingPython
|
||||
SuperBlocks.UpcomingPython,
|
||||
SuperBlocks.A2English
|
||||
],
|
||||
[Languages.ChineseTraditional]: [
|
||||
SuperBlocks.FoundationalCSharp,
|
||||
@@ -117,23 +121,27 @@ export const notAuditedSuperBlocks: NotAuditedSuperBlocks = {
|
||||
SuperBlocks.ProjectEuler,
|
||||
SuperBlocks.JsAlgoDataStructNew,
|
||||
SuperBlocks.TheOdinProject,
|
||||
SuperBlocks.UpcomingPython
|
||||
SuperBlocks.UpcomingPython,
|
||||
SuperBlocks.A2English
|
||||
],
|
||||
[Languages.Italian]: [
|
||||
SuperBlocks.FoundationalCSharp,
|
||||
SuperBlocks.JsAlgoDataStructNew,
|
||||
SuperBlocks.TheOdinProject,
|
||||
SuperBlocks.UpcomingPython
|
||||
SuperBlocks.UpcomingPython,
|
||||
SuperBlocks.A2English
|
||||
],
|
||||
[Languages.Portuguese]: [
|
||||
SuperBlocks.JsAlgoDataStructNew,
|
||||
SuperBlocks.UpcomingPython
|
||||
SuperBlocks.UpcomingPython,
|
||||
SuperBlocks.A2English
|
||||
],
|
||||
[Languages.Ukrainian]: [
|
||||
SuperBlocks.CodingInterviewPrep,
|
||||
SuperBlocks.ProjectEuler,
|
||||
SuperBlocks.JsAlgoDataStructNew,
|
||||
SuperBlocks.UpcomingPython
|
||||
SuperBlocks.UpcomingPython,
|
||||
SuperBlocks.A2English
|
||||
],
|
||||
[Languages.Japanese]: [
|
||||
SuperBlocks.CollegeAlgebraPy,
|
||||
@@ -141,7 +149,8 @@ export const notAuditedSuperBlocks: NotAuditedSuperBlocks = {
|
||||
SuperBlocks.ProjectEuler,
|
||||
SuperBlocks.JsAlgoDataStructNew,
|
||||
SuperBlocks.TheOdinProject,
|
||||
SuperBlocks.UpcomingPython
|
||||
SuperBlocks.UpcomingPython,
|
||||
SuperBlocks.A2English
|
||||
],
|
||||
[Languages.German]: [
|
||||
SuperBlocks.RelationalDb,
|
||||
@@ -154,7 +163,8 @@ export const notAuditedSuperBlocks: NotAuditedSuperBlocks = {
|
||||
SuperBlocks.ProjectEuler,
|
||||
SuperBlocks.JsAlgoDataStructNew,
|
||||
SuperBlocks.TheOdinProject,
|
||||
SuperBlocks.UpcomingPython
|
||||
SuperBlocks.UpcomingPython,
|
||||
SuperBlocks.A2English
|
||||
],
|
||||
[Languages.Arabic]: [
|
||||
SuperBlocks.DataVis,
|
||||
@@ -171,7 +181,8 @@ export const notAuditedSuperBlocks: NotAuditedSuperBlocks = {
|
||||
SuperBlocks.ProjectEuler,
|
||||
SuperBlocks.JsAlgoDataStructNew,
|
||||
SuperBlocks.TheOdinProject,
|
||||
SuperBlocks.UpcomingPython
|
||||
SuperBlocks.UpcomingPython,
|
||||
SuperBlocks.A2English
|
||||
],
|
||||
[Languages.Swahili]: [
|
||||
SuperBlocks.DataVis,
|
||||
@@ -191,7 +202,8 @@ export const notAuditedSuperBlocks: NotAuditedSuperBlocks = {
|
||||
SuperBlocks.FrontEndDevLibs,
|
||||
SuperBlocks.JsAlgoDataStructNew,
|
||||
SuperBlocks.JsAlgoDataStruct,
|
||||
SuperBlocks.UpcomingPython
|
||||
SuperBlocks.UpcomingPython,
|
||||
SuperBlocks.A2English
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ const superBlockFolderMap = {
|
||||
'foundational-c-sharp-with-microsoft':
|
||||
'19-foundational-c-sharp-with-microsoft',
|
||||
'upcoming-python': '20-upcoming-python',
|
||||
'a2-english-for-developers': '21-a2-english-for-developers',
|
||||
'example-certification': '99-example-certification'
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ export function getSuperBlockSubPath(superBlock: SuperBlocks): string {
|
||||
[SuperBlocks.ProjectEuler]: '18-project-euler',
|
||||
[SuperBlocks.FoundationalCSharp]: '19-foundational-c-sharp-with-microsoft',
|
||||
[SuperBlocks.UpcomingPython]: '20-upcoming-python',
|
||||
[SuperBlocks.A2English]: '21-a2-english-for-developers',
|
||||
[SuperBlocks.ExampleCertification]: '99-example-certification'
|
||||
};
|
||||
return pathMap[superBlock];
|
||||
|
||||
@@ -3,6 +3,7 @@ const frontmatter = require('remark-frontmatter');
|
||||
const remark = require('remark-parse');
|
||||
const { readSync } = require('to-vfile');
|
||||
const unified = require('unified');
|
||||
const addFillInTheBlank = require('./plugins/add-fill-in-the-blank');
|
||||
const addFrontmatter = require('./plugins/add-frontmatter');
|
||||
const addSeed = require('./plugins/add-seed');
|
||||
const addSolution = require('./plugins/add-solution');
|
||||
@@ -44,6 +45,7 @@ const processor = unified()
|
||||
// 'directives' will be from text like the css selector :root. These should be
|
||||
// converted back to text before they're added to the challenge object.
|
||||
.use(restoreDirectives)
|
||||
.use(addFillInTheBlank)
|
||||
.use(addVideoQuestion)
|
||||
.use(addAssignment)
|
||||
.use(addTests)
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
const { root } = require('mdast-builder');
|
||||
const find = require('unist-util-find');
|
||||
const getAllBetween = require('./utils/between-headings');
|
||||
const getAllBefore = require('./utils/before-heading');
|
||||
const mdastToHtml = require('./utils/mdast-to-html');
|
||||
|
||||
const { splitOnThematicBreak } = require('./utils/split-on-thematic-break');
|
||||
|
||||
function plugin() {
|
||||
return transformer;
|
||||
function transformer(tree, file) {
|
||||
const fillInTheBlankNodes = getAllBetween(tree, '--fillInTheBlank--');
|
||||
if (fillInTheBlankNodes.length > 0) {
|
||||
const fillInTheBlankTree = root(fillInTheBlankNodes);
|
||||
|
||||
const sentenceNodes = getAllBetween(fillInTheBlankTree, '--sentence--');
|
||||
const blanksNodes = getAllBetween(fillInTheBlankTree, '--blanks--');
|
||||
|
||||
const fillInTheBlank = getfillInTheBlank(sentenceNodes, blanksNodes);
|
||||
|
||||
file.data.fillInTheBlank = fillInTheBlank;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getfillInTheBlank(sentenceNodes, blanksNodes) {
|
||||
const sentence = mdastToHtml(sentenceNodes);
|
||||
const blanks = getBlanks(blanksNodes);
|
||||
|
||||
if (!sentence) throw Error('sentence is missing from fill in the blank');
|
||||
if (!blanks) throw Error('blanks are missing from fill in the blank');
|
||||
if (sentence.match(/_/g).length !== blanks.length)
|
||||
throw Error(
|
||||
`Number of underscores in sentence doesn't match the number of blanks`
|
||||
);
|
||||
|
||||
return { sentence, blanks };
|
||||
}
|
||||
|
||||
function getBlanks(blanksNodes) {
|
||||
const blanksGroups = splitOnThematicBreak(blanksNodes);
|
||||
|
||||
return blanksGroups.map(blanksGroup => {
|
||||
const blanksTree = root(blanksGroup);
|
||||
const feedback = find(blanksTree, { value: '--feedback--' });
|
||||
|
||||
if (feedback) {
|
||||
const blanksNodes = getAllBefore(blanksTree, '--feedback--');
|
||||
const feedbackNodes = getAllBetween(blanksTree, '--feedback--');
|
||||
|
||||
return {
|
||||
answer: blanksNodes[0].children[0].value,
|
||||
feedback: mdastToHtml(feedbackNodes)
|
||||
};
|
||||
}
|
||||
|
||||
return { answer: blanksGroup[0].children[0].value, feedback: null };
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = plugin;
|
||||
@@ -90,7 +90,8 @@ describe('external curriculum data build', () => {
|
||||
'foundational-c-sharp-with-microsoft',
|
||||
'the-odin-project',
|
||||
'upcoming-python',
|
||||
'example-certification'
|
||||
'example-certification',
|
||||
'a2-english-for-developers'
|
||||
];
|
||||
|
||||
// TODO: this is a hack, we should have a single source of truth for the
|
||||
|
||||
Reference in New Issue
Block a user