diff --git a/client/src/components/layouts/global.css b/client/src/components/layouts/global.css index 2463f2e8bc2..8c8b7fd5427 100644 --- a/client/src/components/layouts/global.css +++ b/client/src/components/layouts/global.css @@ -469,6 +469,19 @@ form { input:focus { border-color: var(--tertiary-color); } + +/* + * Text within backticks in en-US, es, and zh-CN challenge files + * is parsed into spans with this class. + */ +.highlighted-text { + padding: 1px 4px; + background-color: var(--tertiary-background); + color: var(--tertiary-color); + border: 1px solid var(--gray-45); + font-size: 90%; +} + code { padding: 1px 4px; background-color: var(--tertiary-background); diff --git a/tools/challenge-parser/parser/__fixtures__/with-en-us-mcq.md b/tools/challenge-parser/parser/__fixtures__/with-en-us-mcq.md new file mode 100644 index 00000000000..1f21d51dd5c --- /dev/null +++ b/tools/challenge-parser/parser/__fixtures__/with-en-us-mcq.md @@ -0,0 +1,36 @@ +--- +id: with-en-us-mcq +title: en-US MCQ +challengeType: 19 +lang: en-US +--- + +# --instructions-- + +Instructions containing `some code`. + +# --questions-- + +## --text-- + +Question text containing `highlighted text`. + +## --answers-- + +`correct answer` + +--- + +`wrong answer` + +### --feedback-- + +Feedback text containing `highlighted text`. + +## --video-solution-- + +1 + +# --explanation-- + +Explanation text containing `highlighted text`. \ No newline at end of file diff --git a/tools/challenge-parser/parser/__fixtures__/with-es-mcq.md b/tools/challenge-parser/parser/__fixtures__/with-es-mcq.md new file mode 100644 index 00000000000..c44e8ad07c5 --- /dev/null +++ b/tools/challenge-parser/parser/__fixtures__/with-es-mcq.md @@ -0,0 +1,36 @@ +--- +id: with-es-mcq +title: Spanish MCQ +challengeType: 19 +lang: es +--- + +# --instructions-- + +Instructions containing `texto resaltado`. + +# --questions-- + +## --text-- + +Question text containing `texto resaltado`. + +## --answers-- + +`correct answer` + +--- + +`wrong answer` + +### --feedback-- + +Feedback text containing `texto resaltado`. + +## --video-solution-- + +1 + +# --explanation-- + +Explanation text containing `texto resaltado`. \ No newline at end of file diff --git a/tools/challenge-parser/parser/plugins/add-quizzes.test.js b/tools/challenge-parser/parser/plugins/add-quizzes.test.js index a3564099a46..e722bd0924d 100644 --- a/tools/challenge-parser/parser/plugins/add-quizzes.test.js +++ b/tools/challenge-parser/parser/plugins/add-quizzes.test.js @@ -103,36 +103,36 @@ describe('add-quizzes plugin', () => { // Quiz 1, Question 2 expect(secondQuestion.text).toBe( - '

Quiz 1, question 2 with 中文

' + '

Quiz 1, question 2 with 中文

' ); expect(secondQuestion.distractors[0]).toBe( - '

Quiz 1, question 2, distractor 1 with 中文

' + '

Quiz 1, question 2, distractor 1 with 中文

' ); expect(secondQuestion.distractors[1]).toBe( - '

Quiz 1, question 2, distractor 2 with 中文

' + '

Quiz 1, question 2, distractor 2 with 中文

' ); expect(secondQuestion.distractors[2]).toBe( - '

Quiz 1, question 2, distractor 3 with 中文

' + '

Quiz 1, question 2, distractor 3 with 中文

' ); expect(secondQuestion.answer).toBe( - '

Quiz 1, question 2, answer with 中文

' + '

Quiz 1, question 2, answer with 中文

' ); // Quiz 1, Question 3 expect(thirdQuestion.text).toBe( - '

Quiz 1, question 3 with zhōng wén

' + '

Quiz 1, question 3 with zhōng wén

' ); expect(thirdQuestion.distractors[0]).toBe( - '

Quiz 1, question 3, distractor 1 with zhōng wén

' + '

Quiz 1, question 3, distractor 1 with zhōng wén

' ); expect(thirdQuestion.distractors[1]).toBe( - '

Quiz 1, question 3, distractor 2 with zhōng wén

' + '

Quiz 1, question 3, distractor 2 with zhōng wén

' ); expect(thirdQuestion.distractors[2]).toBe( - '

Quiz 1, question 3, distractor 3 with zhōng wén

' + '

Quiz 1, question 3, distractor 3 with zhōng wén

' ); expect(thirdQuestion.answer).toBe( - '

Quiz 1, question 3, answer with zhōng wén

' + '

Quiz 1, question 3, answer with zhōng wén

' ); }); }); diff --git a/tools/challenge-parser/parser/plugins/add-text.test.js b/tools/challenge-parser/parser/plugins/add-text.test.js index 9bb839d7351..0d3961a724c 100644 --- a/tools/challenge-parser/parser/plugins/add-text.test.js +++ b/tools/challenge-parser/parser/plugins/add-text.test.js @@ -7,7 +7,9 @@ describe('add-text', () => { mockAST, withSubSectionAST, withNestedInstructionsAST, - withChineseAST; + withChineseAST, + withEnUsAST, + withEsAST; const descriptionId = 'description'; const instructionsId = 'instructions'; const missingId = 'missing'; @@ -21,6 +23,8 @@ describe('add-text', () => { 'with-nested-instructions.md' ); withChineseAST = await parseFixture('with-chinese-mcq.md'); + withEnUsAST = await parseFixture('with-en-us-mcq.md'); + withEsAST = await parseFixture('with-es-mcq.md'); }); beforeEach(() => { @@ -162,6 +166,20 @@ describe('add-text', () => { expect(file.data).toMatchSnapshot(); }); + it('should render inline code as code elements when lang is undefined', () => { + const plugin = addText(['instructions', 'explanation']); + + const defaultFile = { data: {} }; + plugin(withChineseAST, defaultFile); + + expect(defaultFile.data.instructions).toBe( + '
\n

Instructions containing 汉字 (hàn zì).

\n
' + ); + expect(defaultFile.data.explanation).toBe( + '
\n

我是 (wǒ shì) Web 开发者 (kāi fā zhě)。 – I am a web developer.

\n

你好 (nǐ hǎo),我是王华 (wǒ shì Wang Hua),请问你叫什么名字 (qǐng wèn nǐ jiào shén me míng zi)? – Hello, I am Wang Hua, may I ask what your name is?

\n
' + ); + }); + it('should render Chinese inline code as ruby when lang is zh-CN', () => { const plugin = addText(['instructions', 'explanation']); @@ -175,4 +193,32 @@ describe('add-text', () => { '
\n

我是(wǒ shì) Web 开发者(kāi fā zhě)。 – I am a web developer.

\n

你好(nǐ hǎo)我是王华(wǒ shì Wang Hua)请问你叫什么名字(qǐng wèn nǐ jiào shén me míng zi)? – Hello, I am Wang Hua, may I ask what your name is?

\n
' ); }); + + it('should render inline code as span elements when lang is en-US', () => { + const plugin = addText(['instructions', 'explanation']); + + const enUsFile = { data: { lang: 'en-US' } }; + plugin(withEnUsAST, enUsFile); + + expect(enUsFile.data.instructions).toBe( + '
\n

Instructions containing some code.

\n
' + ); + expect(enUsFile.data.explanation).toBe( + '
\n

Explanation text containing highlighted text.

\n
' + ); + }); + + it('should render inline code as span elements when lang is es', () => { + const plugin = addText(['instructions', 'explanation']); + + const esFile = { data: { lang: 'es' } }; + plugin(withEsAST, esFile); + + expect(esFile.data.instructions).toBe( + '
\n

Instructions containing texto resaltado.

\n
' + ); + expect(esFile.data.explanation).toBe( + '
\n

Explanation text containing texto resaltado.

\n
' + ); + }); }); diff --git a/tools/challenge-parser/parser/plugins/add-video-question.test.js b/tools/challenge-parser/parser/plugins/add-video-question.test.js index 9a2ac598001..f817232fdfc 100644 --- a/tools/challenge-parser/parser/plugins/add-video-question.test.js +++ b/tools/challenge-parser/parser/plugins/add-video-question.test.js @@ -8,10 +8,12 @@ describe('add-video-question plugin', () => { multipleQuestionAST, videoOutOfOrderAST, videoWithAudioAST, + chineseVideoAST, + enUsVideoAST, + esVideoAST, videoWithSolutionAboveNumberOfAnswersAST, videoWithFeedbackTwiceInARow, - videoWithCorrectAnswerWithFeedback, - chineseVideoAST; + videoWithCorrectAnswerWithFeedback; const plugin = addVideoQuestion(); let file = { data: {} }; @@ -35,6 +37,8 @@ describe('add-video-question plugin', () => { 'with-video-question-correct-answer-with-feedback.md' ); chineseVideoAST = await parseFixture('with-chinese-mcq.md'); + enUsVideoAST = await parseFixture('with-en-us-mcq.md'); + esVideoAST = await parseFixture('with-es-mcq.md'); }); beforeEach(() => { @@ -211,4 +215,54 @@ describe('add-video-question plugin', () => { '(wèn)' ); }); + + it('should render inline code as spans in question text, answers, and feedback for en-US', async () => { + const enUsFile = { data: { lang: 'en-US' } }; + + plugin(enUsVideoAST, enUsFile); + + const question = enUsFile.data.questions[0]; + + expect(question.text).toBe( + '

Question text containing highlighted text.

' + ); + + const answer1 = question.answers[0]; + expect(answer1.answer).toBe( + '

correct answer

' + ); + + const answer2 = question.answers[1]; + expect(answer2.answer).toBe( + '

wrong answer

' + ); + expect(answer2.feedback).toBe( + '

Feedback text containing highlighted text.

' + ); + }); + + it('should render inline code as spans in question text, answers, and feedback for es', async () => { + const esFile = { data: { lang: 'es' } }; + + plugin(esVideoAST, esFile); + + const question = esFile.data.questions[0]; + + expect(question.text).toBe( + '

Question text containing texto resaltado.

' + ); + + const answer1 = question.answers[0]; + expect(answer1.answer).toBe( + '

correct answer

' + ); + + const answer2 = question.answers[1]; + expect(answer2.answer).toBe( + '

wrong answer

' + ); + expect(answer2.feedback).toBe( + '

Feedback text containing texto resaltado.

' + ); + }); }); diff --git a/tools/challenge-parser/parser/plugins/utils/i18n-stringify.js b/tools/challenge-parser/parser/plugins/utils/i18n-stringify.js index 8f4ab2bcfc5..103f422f9f1 100644 --- a/tools/challenge-parser/parser/plugins/utils/i18n-stringify.js +++ b/tools/challenge-parser/parser/plugins/utils/i18n-stringify.js @@ -91,22 +91,48 @@ function chineseInlineCodeHandler(state, node) { // If static text, return code return { type: 'element', - // TODO: change this to span - // https://github.com/freeCodeCamp/language-curricula/issues/22 - tagName: 'code', - properties: {}, + tagName: 'span', + properties: { className: 'highlighted-text' }, children: [{ type: 'text', value: node.value }] }; } -const rubyOptions = { +/** + * Custom handler for inline code to render as span elements + * @param {object} state - The state object from mdast-util-to-hast + * @param {object} node - The inlineCode node + * @returns {object} Hast element node + */ +function spanInlineCodeHandler(state, node) { + return { + type: 'element', + tagName: 'span', + properties: { className: 'highlighted-text' }, + children: [{ type: 'text', value: node.value }] + }; +} + +const spanOrRubyOptions = { handlers: { inlineCode: chineseInlineCodeHandler } }; -const createMdastToHtml = lang => - lang == 'zh-CN' ? x => mdastToHTML(x, rubyOptions) : mdastToHTML; +const spanOptions = { + handlers: { + inlineCode: spanInlineCodeHandler + } +}; + +const createMdastToHtml = lang => { + if (lang === 'zh-CN') { + return x => mdastToHTML(x, spanOrRubyOptions); + } else if (lang === 'en-US' || lang === 'es') { + return x => mdastToHTML(x, spanOptions); + } else { + return mdastToHTML; + } +}; module.exports = { parseHanziPinyinPairs, diff --git a/tools/challenge-parser/parser/plugins/utils/i18n-stringify.test.js b/tools/challenge-parser/parser/plugins/utils/i18n-stringify.test.js index aff2e3b9110..9b043bd0f60 100644 --- a/tools/challenge-parser/parser/plugins/utils/i18n-stringify.test.js +++ b/tools/challenge-parser/parser/plugins/utils/i18n-stringify.test.js @@ -198,7 +198,7 @@ describe('createMdastToHtml', () => { ); }); - it('should fallback to code element if pattern does not match', () => { + it('should fallback to span element if pattern does not match', () => { const toHtml = createMdastToHtml('zh-CN'); const nodes = [ { @@ -210,11 +210,49 @@ describe('createMdastToHtml', () => { ] } ]; - const actual = toHtml(nodes, { lang: 'zh-CN' }); - expect(actual).toBe('

你好 and nǐ hǎo

'); + const actual = toHtml(nodes); + expect(actual).toBe( + '

你好 and nǐ hǎo

' + ); }); - it('should render as regular code when lang is not zh-CN', () => { + it('should render inline code as span when lang is en-US', () => { + const toHtml = createMdastToHtml('en-US'); + const nodes = [ + { + type: 'paragraph', + children: [ + { type: 'text', value: 'This is ' }, + { type: 'inlineCode', value: 'highlighted text' }, + { type: 'text', value: '.' } + ] + } + ]; + const actual = toHtml(nodes); + expect(actual).toBe( + '

This is highlighted text.

' + ); + }); + + it('should render inline code as span when lang is es', () => { + const toHtml = createMdastToHtml('es'); + const nodes = [ + { + type: 'paragraph', + children: [ + { type: 'text', value: 'Esto texto ' }, + { type: 'inlineCode', value: 'está resaltado' }, + { type: 'text', value: '.' } + ] + } + ]; + const actual = toHtml(nodes); + expect(actual).toBe( + '

Esto texto está resaltado.

' + ); + }); + + it('should render as regular code when lang is not zh-CN, en-US, or es', () => { const toHtml = createMdastToHtml('zh'); const nodes = [ {