Renames, centralize sanitize(), StemTemplate

This commit is contained in:
C. Fuhrman 2025-01-25 14:19:31 -05:00
parent 7dbbfbfe65
commit bb5b2b5e61
17 changed files with 88 additions and 72 deletions

View file

@ -1,4 +1,4 @@
import { textType } from "src/components/GiftTemplate/templates/TextTypeTemplate";
import { FormatTextTemplate } from "src/components/GiftTemplate/templates/TextTypeTemplate";
import { TextFormat } from "gift-pegjs";
describe('TextType', () => {
@ -8,7 +8,7 @@ describe('TextType', () => {
format: 'moodle'
};
const expectedOutput = 'Hello, world! 5 > 3, right?';
expect(textType(input)).toBe(expectedOutput);
expect(FormatTextTemplate(input)).toBe(expectedOutput);
});
it('should format text with newlines correctly', () => {
@ -17,7 +17,7 @@ describe('TextType', () => {
format: 'plain'
};
const expectedOutput = 'Hello,<br>world!<br>5 > 3, right?';
expect(textType(input)).toBe(expectedOutput);
expect(FormatTextTemplate(input)).toBe(expectedOutput);
});
it('should format text with LaTeX correctly', () => {
@ -31,7 +31,7 @@ describe('TextType', () => {
// by running the test and copying the "Received string:" in jest output
// when it fails (assuming the output is correct)
const expectedOutput = '<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">E=mc^2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">E</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8641em;"></span><span class="mord mathnormal">m</span><span class="mord"><span class="mord mathnormal">c</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span>';
expect(textType(input)).toContain(expectedOutput);
expect(FormatTextTemplate(input)).toContain(expectedOutput);
});
it('should format text with two equations (inline and separate) correctly', () => {
@ -41,7 +41,7 @@ describe('TextType', () => {
};
// hint: katex-display is the class that indicates a separate equation
const expectedOutput = '<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>a</mi><mo>+</mo><mi>b</mi><mo>=</mo><mi>c</mi></mrow><annotation encoding="application/x-tex">a + b = c</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">a</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">b</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">c</span></span></span></span> ? <span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">E=mc^2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">E</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8641em;"></span><span class="mord mathnormal">m</span><span class="mord"><span class="mord mathnormal">c</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span>';
expect(textType(input)).toContain(expectedOutput);
expect(FormatTextTemplate(input)).toContain(expectedOutput);
});
it('should format text with a katex matrix correctly', () => {
@ -51,7 +51,7 @@ describe('TextType', () => {
format: 'plain'
};
const expectedOutput = 'Donnez le déterminant de la matrice suivante.<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow></mrow><annotation encoding="application/x-tex"></annotation></semantics></math></span><span class="katex-html" aria-hidden="true"></span></span>\\begin{pmatrix}<br> a&b \\\\<br> c&d<br>\\end{pmatrix}';
expect(textType(input)).toContain(expectedOutput);
expect(FormatTextTemplate(input)).toContain(expectedOutput);
});
it('should format text with Markdown correctly', () => {
@ -61,7 +61,7 @@ describe('TextType', () => {
};
// TODO: investigate why the output has an extra newline
const expectedOutput = '<strong>Bold</strong>\n';
expect(textType(input)).toBe(expectedOutput);
expect(FormatTextTemplate(input)).toBe(expectedOutput);
});
it('should format text with HTML correctly', () => {
@ -70,7 +70,7 @@ describe('TextType', () => {
format: 'html'
};
const expectedOutput = '<em>yes</em>';
expect(textType(input)).toBe(expectedOutput);
expect(FormatTextTemplate(input)).toBe(expectedOutput);
});
it('should format plain text correctly', () => {
@ -79,7 +79,7 @@ describe('TextType', () => {
format: 'plain'
};
const expectedOutput = 'Just plain text';
expect(textType(input)).toBe(expectedOutput);
expect(FormatTextTemplate(input)).toBe(expectedOutput);
});
// Add more tests for other formats if needed

View file

@ -40,7 +40,7 @@ exports[`MultipleChoice snapshot test 1`] = `
&lt;/div&gt;
&lt;p style="
color: hsl(0, 0%, 0%);
"&gt;Sample Stem&lt;/p&gt;&lt;span style="
" class="present-question-stem"&gt;Sample Stem&lt;/p&gt;&lt;span style="
color: hsl(0, 0%, 0%);
"&gt;Choisir une réponse:&lt;/span&gt;
&lt;div class='multiple-choice-answers-container'&gt;
@ -169,7 +169,7 @@ exports[`MultipleChoice snapshot test with 2 images using markdown text format 1
&lt;/div&gt;
&lt;p style="
color: hsl(0, 0%, 0%);
"&gt;&lt;img src="https://via.placeholder.com/150" alt = "Sample Image"/&gt;&lt;/p&gt;&lt;span style="
" class="present-question-stem"&gt;&lt;img src="https://via.placeholder.com/150" alt = "Sample Image"/&gt;&lt;/p&gt;&lt;span style="
color: hsl(0, 0%, 0%);
"&gt;Choisir une réponse:&lt;/span&gt;
&lt;div class='multiple-choice-answers-container'&gt;
@ -337,7 +337,7 @@ exports[`MultipleChoice snapshot test with Moodle text format 1`] = `
&lt;/div&gt;
&lt;p style="
color: hsl(0, 0%, 0%);
"&gt;Sample Stem&lt;/p&gt;&lt;span style="
" class="present-question-stem"&gt;Sample Stem&lt;/p&gt;&lt;span style="
color: hsl(0, 0%, 0%);
"&gt;Choisir une réponse:&lt;/span&gt;
&lt;div class='multiple-choice-answers-container'&gt;
@ -466,7 +466,7 @@ exports[`MultipleChoice snapshot test with image 1`] = `
&lt;/div&gt;
&lt;p style="
color: hsl(0, 0%, 0%);
"&gt;Sample Stem with Image&lt;/p&gt;&lt;span style="
" class="present-question-stem"&gt;Sample Stem with Image&lt;/p&gt;&lt;span style="
color: hsl(0, 0%, 0%);
"&gt;Choisir une réponse:&lt;/span&gt;
&lt;div class='multiple-choice-answers-container'&gt;
@ -631,7 +631,7 @@ exports[`MultipleChoice snapshot test with image using markdown text format 1`]
&lt;/div&gt;
&lt;p style="
color: hsl(0, 0%, 0%);
"&gt;Sample Stem with Image
" class="present-question-stem"&gt;Sample Stem with Image
&lt;/p&gt;&lt;span style="
color: hsl(0, 0%, 0%);
"&gt;Choisir une réponse:&lt;/span&gt;
@ -800,7 +800,7 @@ exports[`MultipleChoice snapshot test with katex 1`] = `
&lt;/div&gt;
&lt;p style="
color: hsl(0, 0%, 0%);
"&gt;&lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML" display="block"&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mi&gt;z&lt;/mi&gt;&lt;mi&gt;z&lt;/mi&gt;&lt;mi&gt;z&lt;/mi&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;annotation encoding="application/x-tex"&gt;\\frac{zzz}{yyy}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class="katex-html" aria-hidden="true"&gt;&lt;span class="base"&gt;&lt;span class="strut" style="height:1.988em;vertical-align:-0.8804em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:1.1076em;"&gt;&lt;span style="top:-2.314em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal" style="margin-right:0.03588em;"&gt;yyy&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-3.23em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="frac-line" style="border-bottom-width:0.04em;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-3.677em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal" style="margin-right:0.04398em;"&gt;zzz&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:0.8804em;"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;span style="
" class="present-question-stem"&gt;&lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML" display="block"&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mi&gt;z&lt;/mi&gt;&lt;mi&gt;z&lt;/mi&gt;&lt;mi&gt;z&lt;/mi&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;annotation encoding="application/x-tex"&gt;\\frac{zzz}{yyy}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class="katex-html" aria-hidden="true"&gt;&lt;span class="base"&gt;&lt;span class="strut" style="height:1.988em;vertical-align:-0.8804em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:1.1076em;"&gt;&lt;span style="top:-2.314em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal" style="margin-right:0.03588em;"&gt;yyy&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-3.23em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="frac-line" style="border-bottom-width:0.04em;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-3.677em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal" style="margin-right:0.04398em;"&gt;zzz&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:0.8804em;"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;span style="
color: hsl(0, 0%, 0%);
"&gt;Choisir une réponse:&lt;/span&gt;
&lt;div class='multiple-choice-answers-container'&gt;
@ -929,7 +929,7 @@ exports[`MultipleChoice snapshot test with katex, using html text format 1`] = `
&lt;/div&gt;
&lt;p style="
color: hsl(0, 0%, 0%);
"&gt;&lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML" display="block"&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mi&gt;z&lt;/mi&gt;&lt;mi&gt;z&lt;/mi&gt;&lt;mi&gt;z&lt;/mi&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;annotation encoding="application/x-tex"&gt;\\frac{zzz}{yyy}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class="katex-html" aria-hidden="true"&gt;&lt;span class="base"&gt;&lt;span class="strut" style="height:1.988em;vertical-align:-0.8804em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:1.1076em;"&gt;&lt;span style="top:-2.314em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal" style="margin-right:0.03588em;"&gt;yyy&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-3.23em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="frac-line" style="border-bottom-width:0.04em;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-3.677em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal" style="margin-right:0.04398em;"&gt;zzz&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:0.8804em;"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;span style="
" class="present-question-stem"&gt;&lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML" display="block"&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mi&gt;z&lt;/mi&gt;&lt;mi&gt;z&lt;/mi&gt;&lt;mi&gt;z&lt;/mi&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;annotation encoding="application/x-tex"&gt;\\frac{zzz}{yyy}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class="katex-html" aria-hidden="true"&gt;&lt;span class="base"&gt;&lt;span class="strut" style="height:1.988em;vertical-align:-0.8804em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:1.1076em;"&gt;&lt;span style="top:-2.314em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal" style="margin-right:0.03588em;"&gt;yyy&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-3.23em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="frac-line" style="border-bottom-width:0.04em;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-3.677em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal" style="margin-right:0.04398em;"&gt;zzz&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:0.8804em;"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;span style="
color: hsl(0, 0%, 0%);
"&gt;Choisir une réponse:&lt;/span&gt;
&lt;div class='multiple-choice-answers-container'&gt;

View file

@ -1,10 +1,8 @@
import { TemplateOptions } from './types';
import QuestionContainer from './QuestionContainerTemplate';
import Title from './TitleTemplate';
import { textType } from './TextTypeTemplate';
import { ParagraphStyle } from '../constants';
import { state } from '.';
import { Description } from 'gift-pegjs';
import StemTemplate from './StemTemplate';
type DescriptionOptions = TemplateOptions & Description;
@ -15,9 +13,7 @@ export default function DescriptionTemplate({ title, formattedStem}: Description
type: 'Description',
title: title
}),
`<p style="${ParagraphStyle(state.theme)}">
${textType(formattedStem)}
</p>`
StemTemplate({formattedStem}),
]
})}`;
}

View file

@ -1,11 +1,11 @@
import { TemplateOptions } from './types';
import QuestionContainer from './QuestionContainerTemplate';
import Title from './TitleTemplate';
import {textType} from './TextTypeTemplate';
import GlobalFeedbackTemplate from './GlobalFeedbackTemplate';
import { ParagraphStyle, TextAreaStyle } from '../constants';
import { TextAreaStyle } from '../constants';
import { state } from '.';
import { EssayQuestion } from 'gift-pegjs';
import StemTemplate from './StemTemplate';
type EssayOptions = TemplateOptions & EssayQuestion;
@ -16,7 +16,7 @@ export default function EssayTemplate({ title, formattedStem, formattedGlobalFee
type: 'Développement',
title: title
}),
`<p style="${ParagraphStyle(state.theme)}">${textType(formattedStem)}</p>`,
StemTemplate({formattedStem}),
`<textarea class="gift-textarea" style="${TextAreaStyle(
state.theme
)}" placeholder="Entrez votre réponse ici..."></textarea>`,

View file

@ -1,5 +1,5 @@
import { TemplateOptions } from './types';
import {textType} from './TextTypeTemplate';
import {FormatTextTemplate} from './TextTypeTemplate';
import { state } from '.';
import { theme } from '../constants';
import { TextFormat } from 'gift-pegjs';
@ -20,7 +20,7 @@ export default function GlobalFeedbackTemplate({ format, text }: GlobalFeedbackO
return (format && text)
? `<div style="${Container}">
<p>${textType({format: format, text: text})}</p>
<p>${FormatTextTemplate({format: format, text: text})}</p>
</div>`
: ``;
}

View file

@ -1,11 +1,12 @@
import { TemplateOptions } from './types';
import QuestionContainer from './QuestionContainerTemplate';
import Title from './TitleTemplate';
import {textType} from './TextTypeTemplate';
import {FormatTextTemplate} from './TextTypeTemplate';
import GlobalFeedback from './GlobalFeedbackTemplate';
import { ParagraphStyle, SelectStyle } from '../constants';
import { state } from '.';
import { MatchingQuestion } from 'gift-pegjs';
import StemTemplate from './StemTemplate';
type MatchingOptions = TemplateOptions & MatchingQuestion;
@ -25,7 +26,7 @@ export default function MatchingTemplate({
type: 'Appariement',
title: title
}),
`<p style="${ParagraphStyle(state.theme)}">${textType(formattedStem)}</p>`,
StemTemplate({formattedStem}),
MatchAnswers({ choices: matchPairs }),
formattedGlobalFeedback ? GlobalFeedback(formattedGlobalFeedback) : ''
]
@ -66,7 +67,7 @@ function MatchAnswers({ choices }: MatchAnswerOptions): string {
.map(({ formattedSubquestion }) => {
return `
<div style="${OptionTable} ${ParagraphStyle(state.theme)}">
${textType(formattedSubquestion)}
${FormatTextTemplate(formattedSubquestion)}
</div>
<div>
<select class="gift-select" style="${SelectStyle(state.theme)} ${Dropdown}">

View file

@ -1,6 +1,6 @@
import { nanoid } from 'nanoid';
import { TemplateOptions } from './types';
import {textType} from './TextTypeTemplate';
import {FormatTextTemplate} from './TextTypeTemplate';
import AnswerIcon from './AnswerIconTemplate';
import { state } from '.';
import { ParagraphStyle, theme } from '../constants';
@ -42,7 +42,7 @@ export default function MultipleChoiceAnswersTemplate({ choices }: MultipleChoic
}" id="${inputId}" name="${id}">
${AnswerWeight({ correct: isCorrectOption, weight: weight })}
<label style="${CustomLabel} ${ParagraphStyle(state.theme)}" for="${inputId}">
${textType(formattedText)}
${FormatTextTemplate(formattedText)}
</label>
${AnswerIcon({ correct: isCorrectOption })}
${AnswerFeedback({ formattedFeedback: formattedFeedback })}
@ -86,5 +86,5 @@ function AnswerFeedback({ formattedFeedback }: AnswerFeedbackOptions): string {
color: ${theme(state.theme, 'teal700', 'gray700')};
`;
return formattedFeedback ? `<span style="${Container}">${textType(formattedFeedback)}</span>` : ``;
return formattedFeedback ? `<span style="${Container}">${FormatTextTemplate(formattedFeedback)}</span>` : ``;
}

View file

@ -2,11 +2,9 @@ import { TemplateOptions } from './types';
import QuestionContainer from './QuestionContainerTemplate';
import GlobalFeedback from './GlobalFeedbackTemplate';
import Title from './TitleTemplate';
import {textType} from './TextTypeTemplate';
import MultipleChoiceAnswers from './MultipleChoiceAnswersTemplate';
import { ParagraphStyle } from '../constants';
import { state } from '.';
import { MultipleChoiceQuestion } from 'gift-pegjs';
import StemTemplate from './StemTemplate';
type MultipleChoiceOptions = TemplateOptions & MultipleChoiceQuestion;
@ -22,7 +20,7 @@ export default function MultipleChoiceTemplate({
type: 'Choix multiple',
title: title
}),
`<p style="${ParagraphStyle(state.theme)}" class="present-question-stem">${textType(formattedStem)}</p>`,
StemTemplate({formattedStem}),
MultipleChoiceAnswers({ choices: choices }),
formattedGlobalFeedback ? GlobalFeedback(formattedGlobalFeedback) : ''
]

View file

@ -1,12 +1,12 @@
import { TemplateOptions } from './types';
import QuestionContainer from './QuestionContainerTemplate';
import Title from './TitleTemplate';
import { textType } from './TextTypeTemplate';
import GlobalFeedback from './GlobalFeedbackTemplate';
import { ParagraphStyle, InputStyle } from '../constants';
import { state } from '.';
import { NumericalAnswer, NumericalQuestion } from 'gift-pegjs';
import { isHighLowNumericalAnswer, isRangeNumericalAnswer, isSimpleNumericalAnswer } from 'gift-pegjs/typeGuards';
import StemTemplate from './StemTemplate';
type NumericalOptions = TemplateOptions & NumericalQuestion;
type NumericalAnswerOptions = TemplateOptions & Pick<NumericalQuestion, 'choices'>;
@ -23,7 +23,7 @@ export default function NumericalTemplate({
type: 'Numérique',
title: title
}),
`<p style="${ParagraphStyle(state.theme)}">${textType(formattedStem)}</p>`,
StemTemplate({formattedStem}),
NumericalAnswers({ choices: choices }),
formattedGlobalFeedback ? GlobalFeedback(formattedGlobalFeedback) : ''
]

View file

@ -1,11 +1,12 @@
import { TemplateOptions } from './types';
import QuestionContainer from './QuestionContainerTemplate';
import Title from './TitleTemplate';
import {textType} from './TextTypeTemplate';
import {FormatTextTemplate} from './TextTypeTemplate';
import GlobalFeedback from './GlobalFeedbackTemplate';
import { ParagraphStyle, InputStyle } from '../constants';
import { state } from './index';
import { ShortAnswerQuestion } from 'gift-pegjs';
import StemTemplate from './StemTemplate';
type ShortAnswerOptions = TemplateOptions & ShortAnswerQuestion;
type AnswerOptions = TemplateOptions & Pick<ShortAnswerQuestion, 'choices'>;
@ -22,7 +23,7 @@ export default function ShortAnswerTemplate({
type: 'Réponse courte',
title: title
}),
`<p style="${ParagraphStyle(state.theme)}">${textType(formattedStem)}</p>`,
StemTemplate({formattedStem}),
Answers({ choices: choices }),
formattedGlobalFeedback ? GlobalFeedback(formattedGlobalFeedback) : ''
]
@ -31,7 +32,7 @@ export default function ShortAnswerTemplate({
function Answers({ choices }: AnswerOptions): string {
const placeholder = choices
.map(({ text }) => textType({ format: '', text: text }))
.map(({ text }) => FormatTextTemplate({ format: '', text: text }))
.join(', ');
return `
<div>

View file

@ -0,0 +1,26 @@
import { TemplateOptions } from './types';
import { state } from '.';
import { ParagraphStyle } from '../constants';
import { BaseQuestion } from 'gift-pegjs';
import { FormatTextTemplate } from './TextTypeTemplate';
// Type is string to allow for custom question type text (e,g, "Multiple Choice")
interface StemOptions extends TemplateOptions {
formattedStem: BaseQuestion['formattedStem'];
}
export default function StemTemplate({ formattedStem }: StemOptions): string {
const Container = `
display: flex;
`;
return `
<div style="${Container}">
<span>
<p style="${ParagraphStyle(state.theme)}" class="present-question-stem">
${FormatTextTemplate(formattedStem)}
</p>
</span>
</div>
`;
}

View file

@ -1,7 +1,7 @@
import { marked } from 'marked';
import katex from 'katex';
import { TextFormat } from 'gift-pegjs';
import DOMPurify from 'dompurify'; // cleans HTML to prevent XSS attacks, etc.
export function formatLatex(text: string): string {
return text
@ -25,7 +25,7 @@ export function formatLatex(text: string): string {
* @see marked
* @see katex
*/
export function textType(formattedText: TextFormat): string {
export function FormatTextTemplate(formattedText: TextFormat): string {
const formatText = formatLatex(formattedText.text.trim()); // latex needs pure "&", ">", etc. Must not be escaped
let parsedText = '';
switch (formattedText.format) {
@ -33,13 +33,13 @@ export function textType(formattedText: TextFormat): string {
case 'moodle':
case 'plain':
// Replace newlines with <br> tags
return formatText.replace(/(?:\r\n|\r|\n)/g, '<br>');
return DOMPurify.sanitize(formatText.replace(/(?:\r\n|\r|\n)/g, '<br>'));
case 'html':
// Strip outer paragraph tags (not a great approach with regex)
return formatText.replace(/(^<p>)(.*?)(<\/p>)$/gm, '$2');
return DOMPurify.sanitize(formatText.replace(/(^<p>)(.*?)(<\/p>)$/gm, '$2'));
case 'markdown':
parsedText = marked.parse(formatText, { breaks: true }) as string; // https://github.com/markedjs/marked/discussions/3219
return parsedText.replace(/(^<p>)(.*?)(<\/p>)$/gm, '$2');
return DOMPurify.sanitize(parsedText.replace(/(^<p>)(.*?)(<\/p>)$/gm, '$2'));
default:
throw new Error(`Unsupported text format: ${formattedText.format}`);
}

View file

@ -1,13 +1,10 @@
import { TemplateOptions } from './types';
import QuestionContainer from './QuestionContainerTemplate';
import {textType} from './TextTypeTemplate';
import GlobalFeedback from './GlobalFeedbackTemplate';
import MultipleChoiceAnswersTemplate from './MultipleChoiceAnswersTemplate';
import Title from './TitleTemplate';
import { TextChoice, TrueFalseQuestion } from 'gift-pegjs';
import { ParagraphStyle } from '../constants';
import { state } from '.';
import DOMPurify from 'dompurify';
import StemTemplate from './StemTemplate';
type TrueFalseOptions = TemplateOptions & TrueFalseQuestion;
@ -42,7 +39,7 @@ export default function TrueFalseTemplate({
type: 'Vrai/Faux',
title: title
}),
`<p style="${ParagraphStyle(state.theme)}" class="present-question-stem">${DOMPurify.sanitize(textType(formattedStem))}</p>`,
StemTemplate({formattedStem}),
MultipleChoiceAnswersTemplate({ choices: choices }),
formattedGlobalFeedback ? GlobalFeedback(formattedGlobalFeedback) : ``
]

View file

@ -2,9 +2,8 @@
import React, { useEffect, useState } from 'react';
import '../questionStyle.css';
import { Button } from '@mui/material';
import { textType } from '../../GiftTemplate/templates/TextTypeTemplate';
import { FormatTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate';
import { MultipleChoiceQuestion } from 'gift-pegjs';
import DOMPurify from 'dompurify';
interface Props {
question: MultipleChoiceQuestion;
@ -31,7 +30,7 @@ const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
return (
<div className="question-container">
<div className="question content">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedStem)) }} />
<div dangerouslySetInnerHTML={{ __html: FormatTextTemplate(question.formattedStem) }} />
</div>
<div className="choices-wrapper mb-1">
{question.choices.map((choice, i) => {
@ -48,13 +47,13 @@ const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
(choice.isCorrect ? '✅' : '❌')}
<div className={`circle ${selected}`}>{alphabet[i]}</div>
<div className={`answer-text ${selected}`}>
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(choice.formattedText)) }} />
<div dangerouslySetInnerHTML={{ __html: FormatTextTemplate(choice.formattedText) }} />
</div>
</Button>
{choice.formattedFeedback && showAnswer && (
<div className="feedback-container mb-1 mt-1/2">
{choice.isCorrect ? '✅' : '❌'}
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(choice.formattedFeedback)) }} />
<div dangerouslySetInnerHTML={{ __html: FormatTextTemplate(choice.formattedFeedback) }} />
</div>
)}
</div>
@ -63,7 +62,7 @@ const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
</div>
{question.formattedGlobalFeedback && showAnswer && (
<div className="global-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedGlobalFeedback)) }} />
<div dangerouslySetInnerHTML={{ __html: FormatTextTemplate(question.formattedGlobalFeedback) }} />
</div>
)}

View file

@ -2,10 +2,9 @@
import React, { useState } from 'react';
import '../questionStyle.css';
import { Button, TextField } from '@mui/material';
import { textType } from '../../GiftTemplate/templates/TextTypeTemplate';
import { FormatTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate';
import { NumericalQuestion, SimpleNumericalAnswer, RangeNumericalAnswer, HighLowNumericalAnswer } from 'gift-pegjs';
import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer, isMultipleNumericalAnswer } from 'gift-pegjs/typeGuards';
import DOMPurify from 'dompurify';
interface Props {
question: NumericalQuestion;
@ -41,13 +40,13 @@ const NumericalQuestionDisplay: React.FC<Props> = (props) => {
return (
<div className="question-wrapper">
<div>
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedStem)) }} />
<div dangerouslySetInnerHTML={{ __html: FormatTextTemplate(question.formattedStem) }} />
</div>
{showAnswer ? (
<>
<div className="correct-answer-text mb-2">{correctAnswer}</div>
{question.formattedGlobalFeedback && <div className="global-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedGlobalFeedback)) }} />
<div dangerouslySetInnerHTML={{ __html: FormatTextTemplate(question.formattedGlobalFeedback) }} />
</div>}
</>
) : (
@ -65,7 +64,7 @@ const NumericalQuestionDisplay: React.FC<Props> = (props) => {
</div>
{question.formattedGlobalFeedback && showAnswer && (
<div className="global-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedGlobalFeedback)) }} />
<div dangerouslySetInnerHTML={{ __html: FormatTextTemplate(question.formattedGlobalFeedback) }} />
</div>
)}
{handleOnSubmitAnswer && (

View file

@ -1,9 +1,8 @@
import React, { useState } from 'react';
import '../questionStyle.css';
import { Button, TextField } from '@mui/material';
import { textType } from '../../GiftTemplate/templates/TextTypeTemplate';
import { FormatTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate';
import { ShortAnswerQuestion } from 'gift-pegjs';
import DOMPurify from 'dompurify';
interface Props {
question: ShortAnswerQuestion;
@ -18,7 +17,7 @@ const ShortAnswerQuestionDisplay: React.FC<Props> = (props) => {
return (
<div className="question-wrapper">
<div className="question content">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedStem)) }} />
<div dangerouslySetInnerHTML={{ __html: FormatTextTemplate(question.formattedStem) }} />
</div>
{showAnswer ? (
<>
@ -30,7 +29,7 @@ const ShortAnswerQuestionDisplay: React.FC<Props> = (props) => {
))}
</div>
{question.formattedGlobalFeedback && <div className="global-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedGlobalFeedback)) }} />
<div dangerouslySetInnerHTML={{ __html: FormatTextTemplate(question.formattedGlobalFeedback) }} />
</div>}
</>
) : (

View file

@ -4,7 +4,7 @@ import '../questionStyle.css';
import { Button } from '@mui/material';
import { TrueFalseQuestion } from 'gift-pegjs';
import DOMPurify from 'dompurify';
import { textType } from 'src/components/GiftTemplate/templates/TextTypeTemplate';
import { FormatTextTemplate } from 'src/components/GiftTemplate/templates/TextTypeTemplate';
interface Props {
question: TrueFalseQuestion;
@ -27,7 +27,7 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
return (
<div className="question-container">
<div className="question content">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedStem)) }} />
<div dangerouslySetInnerHTML={{ __html: FormatTextTemplate(question.formattedStem) }} />
</div>
<div className="choices-wrapper mb-1">
<Button
@ -52,18 +52,18 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
{/* selected TRUE, show True feedback if it exists */}
{showAnswer && answer && question.trueFormattedFeedback && (
<div className="true-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.trueFormattedFeedback)) }} />
<div dangerouslySetInnerHTML={{ __html: FormatTextTemplate(question.trueFormattedFeedback) }} />
</div>
)}
{/* selected FALSE, show False feedback if it exists */}
{showAnswer && !answer && question.falseFormattedFeedback && (
<div className="false-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.falseFormattedFeedback)) }} />
<div dangerouslySetInnerHTML={{ __html: FormatTextTemplate(question.falseFormattedFeedback) }} />
</div>
)}
{question.formattedGlobalFeedback && showAnswer && (
<div className="global-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedGlobalFeedback)) }} />
<div dangerouslySetInnerHTML={{ __html: FormatTextTemplate(question.formattedGlobalFeedback) }} />
</div>
)}
{!showAnswer && handleOnSubmitAnswer && (