mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
Merge branch 'main' into add-composant-and-snapshot-tests-for-the-EditorQuiz
This commit is contained in:
commit
a04ec3a2e7
11 changed files with 113 additions and 12 deletions
23
client/package-lock.json
generated
23
client/package-lock.json
generated
|
|
@ -19,6 +19,7 @@
|
||||||
"@mui/material": "^6.1.0",
|
"@mui/material": "^6.1.0",
|
||||||
"@types/uuid": "^9.0.7",
|
"@types/uuid": "^9.0.7",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
|
"dompurify": "^3.2.3",
|
||||||
"esbuild": "^0.23.1",
|
"esbuild": "^0.23.1",
|
||||||
"gift-pegjs": "^1.0.2",
|
"gift-pegjs": "^1.0.2",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
|
|
@ -4705,6 +4706,13 @@
|
||||||
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
|
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/trusted-types": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/@types/unist": {
|
"node_modules/@types/unist": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
||||||
|
|
@ -6230,6 +6238,15 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dompurify": {
|
||||||
|
"version": "3.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.3.tgz",
|
||||||
|
"integrity": "sha512-U1U5Hzc2MO0oW3DF+G9qYN0aT7atAou4AgI0XjWz061nyBPbdxkfdhfy5uMgGn6+oLFCfn44ZGbdDqCzVmlOWA==",
|
||||||
|
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@types/trusted-types": "^2.0.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dunder-proto": {
|
"node_modules/dunder-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
|
|
@ -10818,9 +10835,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss/node_modules/nanoid": {
|
"node_modules/postcss/node_modules/nanoid": {
|
||||||
"version": "3.3.7",
|
"version": "3.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
"@mui/material": "^6.1.0",
|
"@mui/material": "^6.1.0",
|
||||||
"@types/uuid": "^9.0.7",
|
"@types/uuid": "^9.0.7",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
|
"dompurify": "^3.2.3",
|
||||||
"esbuild": "^0.23.1",
|
"esbuild": "^0.23.1",
|
||||||
"gift-pegjs": "^1.0.2",
|
"gift-pegjs": "^1.0.2",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@ import React from 'react';
|
||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
import AnswerIcon from 'src/components/GiftTemplate/templates/AnswerIcon';
|
import AnswerIcon from 'src/components/GiftTemplate/templates/AnswerIcon';
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
|
||||||
describe('AnswerIcon', () => {
|
describe('AnswerIcon', () => {
|
||||||
test('renders correct icon when correct is true', () => {
|
test('renders correct icon when correct is true', () => {
|
||||||
const { container } = render(<div dangerouslySetInnerHTML={{ __html: AnswerIcon({ correct: true }) }} />);
|
const { container } = render(<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(AnswerIcon({ correct: true })) }} />);
|
||||||
const svgElement = container.querySelector('svg');
|
const svgElement = container.querySelector('svg');
|
||||||
|
|
||||||
expect(svgElement).toBeInTheDocument();
|
expect(svgElement).toBeInTheDocument();
|
||||||
|
|
@ -20,7 +21,7 @@ describe('AnswerIcon', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders incorrect icon when correct is false', () => {
|
test('renders incorrect icon when correct is false', () => {
|
||||||
const { container } = render(<div dangerouslySetInnerHTML={{ __html: AnswerIcon({ correct: false }) }} />);
|
const { container } = render(<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(AnswerIcon({ correct: false })) }} />);
|
||||||
const svgElement = container.querySelector('svg');
|
const svgElement = container.querySelector('svg');
|
||||||
|
|
||||||
expect(svgElement).toBeInTheDocument();
|
expect(svgElement).toBeInTheDocument();
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import React, { useEffect, useState } from 'react';
|
||||||
import Template, { ErrorTemplate } from './templates';
|
import Template, { ErrorTemplate } from './templates';
|
||||||
import { parse } from 'gift-pegjs';
|
import { parse } from 'gift-pegjs';
|
||||||
import './styles.css';
|
import './styles.css';
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
|
||||||
interface GIFTTemplatePreviewProps {
|
interface GIFTTemplatePreviewProps {
|
||||||
questions: string[];
|
questions: string[];
|
||||||
|
|
@ -73,7 +74,7 @@ const GIFTTemplatePreview: React.FC<GIFTTemplatePreviewProps> = ({
|
||||||
<div className="error">{error}</div>
|
<div className="error">{error}</div>
|
||||||
) : isPreviewReady ? (
|
) : isPreviewReady ? (
|
||||||
<div data-testid="preview-container">
|
<div data-testid="preview-container">
|
||||||
<div dangerouslySetInnerHTML={{ __html: items }}></div>
|
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(items) }}></div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="loading">Chargement de la prévisualisation...</div>
|
<div className="loading">Chargement de la prévisualisation...</div>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import '../questionStyle.css';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
import textType, { formatLatex } from '../../GiftTemplate/templates/TextType';
|
import textType, { formatLatex } from '../../GiftTemplate/templates/TextType';
|
||||||
import { TextFormat } from '../../GiftTemplate/templates/types';
|
import { TextFormat } from '../../GiftTemplate/templates/types';
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
// import Latex from 'react-latex';
|
// import Latex from 'react-latex';
|
||||||
|
|
||||||
type Choices = {
|
type Choices = {
|
||||||
|
|
@ -39,7 +40,7 @@ const MultipleChoiceQuestion: React.FC<Props> = (props) => {
|
||||||
return (
|
return (
|
||||||
<div className="question-container">
|
<div className="question-container">
|
||||||
<div className="question content">
|
<div className="question content">
|
||||||
<div dangerouslySetInnerHTML={{ __html: textType({text: questionContent}) }} />
|
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({text: questionContent})) }} />
|
||||||
</div>
|
</div>
|
||||||
<div className="choices-wrapper mb-1">
|
<div className="choices-wrapper mb-1">
|
||||||
{choices.map((choice, i) => {
|
{choices.map((choice, i) => {
|
||||||
|
|
@ -56,7 +57,7 @@ const MultipleChoiceQuestion: React.FC<Props> = (props) => {
|
||||||
(choice.isCorrect ? '✅' : '❌')}
|
(choice.isCorrect ? '✅' : '❌')}
|
||||||
<div className={`circle ${selected}`}>{alphabet[i]}</div>
|
<div className={`circle ${selected}`}>{alphabet[i]}</div>
|
||||||
<div className={`answer-text ${selected}`}>
|
<div className={`answer-text ${selected}`}>
|
||||||
<div dangerouslySetInnerHTML={{ __html: formatLatex(choice.text.text) }} />
|
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(formatLatex(choice.text.text)) }} />
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
{choice.feedback && showAnswer && (
|
{choice.feedback && showAnswer && (
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import '../questionStyle.css';
|
||||||
import { Button, TextField } from '@mui/material';
|
import { Button, TextField } from '@mui/material';
|
||||||
import textType from '../../GiftTemplate/templates/TextType';
|
import textType from '../../GiftTemplate/templates/TextType';
|
||||||
import { TextFormat } from '../../GiftTemplate/templates/types';
|
import { TextFormat } from '../../GiftTemplate/templates/types';
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
|
||||||
type CorrectAnswer = {
|
type CorrectAnswer = {
|
||||||
numberHigh?: number;
|
numberHigh?: number;
|
||||||
|
|
@ -34,7 +35,7 @@ const NumericalQuestion: React.FC<Props> = (props) => {
|
||||||
return (
|
return (
|
||||||
<div className="question-wrapper">
|
<div className="question-wrapper">
|
||||||
<div>
|
<div>
|
||||||
<div dangerouslySetInnerHTML={{ __html: textType({text: questionContent}) }} />
|
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({text: questionContent})) }} />
|
||||||
</div>
|
</div>
|
||||||
{showAnswer ? (
|
{showAnswer ? (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import '../questionStyle.css';
|
||||||
import { Button, TextField } from '@mui/material';
|
import { Button, TextField } from '@mui/material';
|
||||||
import textType from '../../GiftTemplate/templates/TextType';
|
import textType from '../../GiftTemplate/templates/TextType';
|
||||||
import { TextFormat } from '../../GiftTemplate/templates/types';
|
import { TextFormat } from '../../GiftTemplate/templates/types';
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
|
||||||
type Choices = {
|
type Choices = {
|
||||||
feedback: { format: string; text: string } | null;
|
feedback: { format: string; text: string } | null;
|
||||||
|
|
@ -28,7 +29,7 @@ const ShortAnswerQuestion: React.FC<Props> = (props) => {
|
||||||
return (
|
return (
|
||||||
<div className="question-wrapper">
|
<div className="question-wrapper">
|
||||||
<div className="question content">
|
<div className="question content">
|
||||||
<div dangerouslySetInnerHTML={{ __html: textType({text: questionContent}) }} />
|
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({text: questionContent})) }} />
|
||||||
</div>
|
</div>
|
||||||
{showAnswer ? (
|
{showAnswer ? (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import '../questionStyle.css';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
import textType from '../../GiftTemplate/templates/TextType';
|
import textType from '../../GiftTemplate/templates/TextType';
|
||||||
import { TextFormat } from '../../GiftTemplate/templates/types';
|
import { TextFormat } from '../../GiftTemplate/templates/types';
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
questionContent: TextFormat;
|
questionContent: TextFormat;
|
||||||
|
|
@ -27,7 +28,7 @@ const TrueFalseQuestion: React.FC<Props> = (props) => {
|
||||||
return (
|
return (
|
||||||
<div className="question-container">
|
<div className="question-container">
|
||||||
<div className="question content">
|
<div className="question content">
|
||||||
<div dangerouslySetInnerHTML={{ __html: textType({ text: questionContent }) }} />
|
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({ text: questionContent })) }} />
|
||||||
</div>
|
</div>
|
||||||
<div className="choices-wrapper mb-1">
|
<div className="choices-wrapper mb-1">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// EditorQuiz.tsx
|
// EditorQuiz.tsx
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef, CSSProperties } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { FolderType } from '../../../Types/FolderType';
|
import { FolderType } from '../../../Types/FolderType';
|
||||||
|
|
@ -40,6 +40,26 @@ const QuizForm: React.FC = () => {
|
||||||
};
|
};
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
|
const [showScrollButton, setShowScrollButton] = useState(false);
|
||||||
|
|
||||||
|
const scrollToTop = () => {
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleScroll = () => {
|
||||||
|
if (window.scrollY > 300) {
|
||||||
|
setShowScrollButton(true);
|
||||||
|
} else {
|
||||||
|
setShowScrollButton(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('scroll', handleScroll);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('scroll', handleScroll);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
|
|
@ -292,8 +312,32 @@ const QuizForm: React.FC = () => {
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{showScrollButton && (
|
||||||
|
<Button
|
||||||
|
onClick={scrollToTop}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
style={scrollToTopButtonStyle}
|
||||||
|
title="Scroll to top"
|
||||||
|
>
|
||||||
|
↑
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const scrollToTopButtonStyle: CSSProperties = {
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: '40px',
|
||||||
|
right: '50px',
|
||||||
|
padding: '10px',
|
||||||
|
fontSize: '16px',
|
||||||
|
color: 'white',
|
||||||
|
backgroundColor: '#5271ff',
|
||||||
|
border: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
zIndex: 1000,
|
||||||
|
};
|
||||||
|
|
||||||
export default QuizForm;
|
export default QuizForm;
|
||||||
|
|
|
||||||
28
package-lock.json
generated
Normal file
28
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"name": "EvalueTonSavoir",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"dompurify": "^3.2.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/trusted-types": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/dompurify": {
|
||||||
|
"version": "3.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.3.tgz",
|
||||||
|
"integrity": "sha512-U1U5Hzc2MO0oW3DF+G9qYN0aT7atAou4AgI0XjWz061nyBPbdxkfdhfy5uMgGn6+oLFCfn44ZGbdDqCzVmlOWA==",
|
||||||
|
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@types/trusted-types": "^2.0.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
package.json
Normal file
5
package.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"dompurify": "^3.2.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue