clean HTMLtoImgtoPDF

This commit is contained in:
Philippe 2025-02-05 14:53:53 -05:00
parent f8b1a8421b
commit 4ae5561154
3 changed files with 544 additions and 18 deletions

View file

@ -1,4 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Dialog, DialogTitle, DialogActions, Button, Tooltip, IconButton } from '@mui/material'; import { Dialog, DialogTitle, DialogActions, Button, Tooltip, IconButton } from '@mui/material';
import { FileDownload } from '@mui/icons-material'; import { FileDownload } from '@mui/icons-material';
@ -6,6 +7,10 @@ import { QuizType } from '../../Types/QuizType';
import ApiService from '../../services/ApiService'; import ApiService from '../../services/ApiService';
import jsPDF from 'jspdf'; import jsPDF from 'jspdf';
import html2canvas from 'html2canvas';
import { parse } from 'gift-pegjs';
import DOMPurify from 'dompurify';
import Template, { ErrorTemplate } from '../GiftTemplate/templates';
interface DownloadQuizModalProps { interface DownloadQuizModalProps {
quiz: QuizType; quiz: QuizType;
@ -59,32 +64,86 @@ const DownloadQuizModal: React.FC<DownloadQuizModalProps> = ({ quiz }) => {
const selectedQuiz = await ApiService.getQuiz(quiz._id) as QuizType; const selectedQuiz = await ApiService.getQuiz(quiz._id) as QuizType;
if (!selectedQuiz) throw new Error('Quiz not found'); if (!selectedQuiz) throw new Error('Quiz not found');
const doc = new jsPDF();
doc.setFont("helvetica", "bold");
doc.setFontSize(25);
doc.text(selectedQuiz.title, 10, 15);
let yPosition = 40; let previewHTML = '';
doc.setFont("helvetica", "normal"); selectedQuiz.content.forEach((giftQuestion) => {
doc.setFontSize(16); try {
const question = parse(giftQuestion);
selectedQuiz.content.forEach((question, index) => { previewHTML += Template(question[0], {
const formattedQuestion = withAnswers preview: true,
? question theme: 'light',
: question.replace(/\{[^}]+\}/g, ''); });
} catch (error) {
const wrappedText = doc.splitTextToSize(`${index + 1}. ${formattedQuestion}`, 180); if (error instanceof Error) {
previewHTML += ErrorTemplate(giftQuestion + '\n' + error.message);
doc.text(wrappedText, 10, yPosition); } else {
previewHTML += ErrorTemplate(giftQuestion + '\n' + 'Erreur inconnue');
yPosition += wrappedText.length * 10; }
}
}); });
if (!withAnswers) {
const svgRegex = /<svg[^>]*>([\s\S]*?)<\/svg>/gi;
previewHTML = previewHTML.replace(svgRegex, '');
const placeholderRegex = /(placeholder=")[^"]*(")/gi;
previewHTML = previewHTML.replace(placeholderRegex, '$1$2');
const feedbackContainerRegex = /<(div|span)[^>]*class="feedback-container"[^>]*>[\s\S]*?<\/div>/gi;
previewHTML = previewHTML.replace(feedbackContainerRegex, '');
const answerClassRegex = /<(div|span)[^>]*class="[^"]*answer[^"]*"[^>]*>[\s\S]*?<\/\1>/gi;
previewHTML = previewHTML.replace(answerClassRegex, '');
const bonneReponseRegex = /<p[^>]*>[^<]*bonne réponse[^<]*<\/p>/gi;
previewHTML = previewHTML.replace(bonneReponseRegex, '');
const AllAnswersFieldRegex = /<(p|span)[^>]*>\s*Réponse:\s*<\/\1>\s*<input[^>]*>/gi
previewHTML = previewHTML.replace(AllAnswersFieldRegex, '');
}
const sanitizedHTML = DOMPurify.sanitize(previewHTML);
console.log('previewHTML:', sanitizedHTML);
const tempDiv = document.createElement('div');
tempDiv.innerHTML = sanitizedHTML;
document.body.appendChild(tempDiv);
const canvas = await html2canvas(tempDiv, { scale: 2 });
document.body.removeChild(tempDiv);
const pdf = new jsPDF('p', 'mm', 'a4');
const pageWidth = pdf.internal.pageSize.width;
const pageHeight = pdf.internal.pageSize.height;
const margin = 10;
const imgWidth = pageWidth - 2 * margin;
let yOffset = 0;
while (yOffset < canvas.height) {
const pageCanvas = document.createElement('canvas');
pageCanvas.width = canvas.width;
pageCanvas.height = Math.min(canvas.height - yOffset, (pageHeight - 2 * margin) * (canvas.width / imgWidth));
const pageCtx = pageCanvas.getContext('2d');
if (pageCtx) {
pageCtx.drawImage(canvas, 0, yOffset, canvas.width, pageCanvas.height, 0, 0, pageCanvas.width, pageCanvas.height);
}
const pageImgData = pageCanvas.toDataURL('image/png');
if (yOffset > 0) pdf.addPage();
pdf.addImage(pageImgData, 'PNG', margin, margin, imgWidth, (pageCanvas.height * imgWidth) / pageCanvas.width);
yOffset += pageCanvas.height;
}
const filename = withAnswers const filename = withAnswers
? `${selectedQuiz.title}_avec_reponses.pdf` ? `${selectedQuiz.title}_avec_reponses.pdf`
: `${selectedQuiz.title}_sans_reponses.pdf`; : `${selectedQuiz.title}_sans_reponses.pdf`;
doc.save(filename); pdf.save(filename);
} catch (error) { } catch (error) {
console.error('Error exporting quiz as PDF:', error); console.error('Error exporting quiz as PDF:', error);
} }

View file

@ -0,0 +1,461 @@
<section style="flex-wrap: wrap;
position: relative;
padding: 1rem 1rem;
margin-bottom: 0.5rem;
background-color: hsl(0, 0%, 100%);
border: solid hsl(0, 0%, 100%) 2px;
border-radius: 6px;
box-shadow: 0px 1px 3px hsl(0, 0%, 74%);">
<div style="display: flex;
font-weight: bold;">
<span>
<span style="color: #5271FF;"><em>(Sans titre)</em></span>
</span>
<span style="margin-left: auto;
padding-left: 0.75rem;
flex: none;
margin-bottom: 10px;">
<span style="box-shadow: 0px 1px 3px hsl(0, 0%, 74%);
padding-left: 0.7rem;
padding-right: 0.7rem;
padding-top: 0.4rem;
padding-bottom: 0.4rem;
border-radius: 4px;
background-color: hsl(0, 0%, 100%);
color: hsl(180, 15%, 41%);">Choix multiple</span>
</span>
</div>
<div style="display: flex;">
<span>
<p class="present-question-stem" style="color: hsl(0, 0%, 0%);">
Quelle ville est la capitale du Canada?
</p>
</span>
</div>
<span style="color: hsl(0, 0%, 0%);">Choisir une réponse:</span>
<div class="multiple-choice-answers-container">
<input name="idCO6FDyZe" id="idMhfh1m" type="radio" class="gift-input">
<label for="idMhfh1m" style="display: inline-block;
padding: 0.2em 0 0.2em 0;
color: hsl(0, 0%, 0%);">
Toronto
</label>
</div>
<div class="multiple-choice-answers-container">
<input name="idCO6FDyZe" id="idE3YQnW" type="radio" class="gift-input">
<label for="idE3YQnW" style="display: inline-block;
padding: 0.2em 0 0.2em 0;
color: hsl(0, 0%, 0%);">
Montréal
</label>
</div>
<div class="multiple-choice-answers-container">
<input name="idCO6FDyZe" id="id9UoyK7" type="radio" class="gift-input">
<label for="id9UoyK7" style="display: inline-block;
padding: 0.2em 0 0.2em 0;
color: hsl(0, 0%, 0%);">
Ottawa
</label>
</div></section><section style="flex-wrap: wrap;
position: relative;
padding: 1rem 1rem;
margin-bottom: 0.5rem;
background-color: hsl(0, 0%, 100%);
border: solid hsl(0, 0%, 100%) 2px;
border-radius: 6px;
box-shadow: 0px 1px 3px hsl(0, 0%, 74%);">
<div style="display: flex;
font-weight: bold;">
<span>
<span style="color: #5271FF;"><em>(Sans titre)</em></span>
</span>
<span style="margin-left: auto;
padding-left: 0.75rem;
flex: none;
margin-bottom: 10px;">
<span style="box-shadow: 0px 1px 3px hsl(0, 0%, 74%);
padding-left: 0.7rem;
padding-right: 0.7rem;
padding-top: 0.4rem;
padding-bottom: 0.4rem;
border-radius: 4px;
background-color: hsl(0, 0%, 100%);
color: hsl(180, 15%, 41%);">Réponse courte</span>
</span>
</div>
<div style="display: flex;">
<span>
<p class="present-question-stem" style="color: hsl(0, 0%, 0%);">
Avec quoi ouvre-t-on une porte?
</p>
</span>
</div>
<div>
<span style="color: hsl(0, 0%, 0%);">Réponse: </span><input placeholder="" style="display: inline-block;
padding: 0.375rem 0.75rem;
line-height: 1.5;
color: hsl(0, 0%, 16%);
background-color: hsl(0, 0%, 100%);
border: 1px solid hsl(0, 0%, 81%);
border-radius: 0.25rem;
box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
width: 90%;" type="text" class="gift-input">
</div>
</section><section style="flex-wrap: wrap;
position: relative;
padding: 1rem 1rem;
margin-bottom: 0.5rem;
background-color: hsl(0, 0%, 100%);
border: solid hsl(0, 0%, 100%) 2px;
border-radius: 6px;
box-shadow: 0px 1px 3px hsl(0, 0%, 74%);">
<div style="display: flex;
font-weight: bold;">
<span>
<span style="color: #5271FF;"><em>(Sans titre)</em></span>
</span>
<span style="margin-left: auto;
padding-left: 0.75rem;
flex: none;
margin-bottom: 10px;">
<span style="box-shadow: 0px 1px 3px hsl(0, 0%, 74%);
padding-left: 0.7rem;
padding-right: 0.7rem;
padding-top: 0.4rem;
padding-bottom: 0.4rem;
border-radius: 4px;
background-color: hsl(0, 0%, 100%);
color: hsl(180, 15%, 41%);">Numérique</span>
</span>
</div>
<div style="display: flex;">
<span>
<p class="present-question-stem" style="color: hsl(0, 0%, 0%);">
Quel est un nombre de 1 à 5&nbsp;?
</p>
</span>
</div>
<div><p style="color: hsl(0, 0%, 0%);">Réponse: </p><input placeholder="" style="display: inline-block;
padding: 0.375rem 0.75rem;
line-height: 1.5;
color: hsl(0, 0%, 16%);
background-color: hsl(0, 0%, 100%);
border: 1px solid hsl(0, 0%, 81%);
border-radius: 0.25rem;
box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
width: 90%;
;width: 100%" type="text" class="gift-input"></div></section><section style="flex-wrap: wrap;
position: relative;
padding: 1rem 1rem;
margin-bottom: 0.5rem;
background-color: hsl(0, 0%, 100%);
border: solid hsl(0, 0%, 100%) 2px;
border-radius: 6px;
box-shadow: 0px 1px 3px hsl(0, 0%, 74%);">
<div style="display: flex;
font-weight: bold;">
<span>
<span style="color: #5271FF;"><em>(Sans titre)</em></span>
</span>
<span style="margin-left: auto;
padding-left: 0.75rem;
flex: none;
margin-bottom: 10px;">
<span style="box-shadow: 0px 1px 3px hsl(0, 0%, 74%);
padding-left: 0.7rem;
padding-right: 0.7rem;
padding-top: 0.4rem;
padding-bottom: 0.4rem;
border-radius: 4px;
background-color: hsl(0, 0%, 100%);
color: hsl(180, 15%, 41%);">Numérique</span>
</span>
</div>
<div style="display: flex;">
<span>
<p class="present-question-stem" style="color: hsl(0, 0%, 0%);">
Quel est un nombre de 1 à 5&nbsp;?
</p>
</span>
</div>
<div><p style="color: hsl(0, 0%, 0%);">Réponse: </p><input placeholder="" style="display: inline-block;
padding: 0.375rem 0.75rem;
line-height: 1.5;
color: hsl(0, 0%, 16%);
background-color: hsl(0, 0%, 100%);
border: 1px solid hsl(0, 0%, 81%);
border-radius: 0.25rem;
box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
width: 90%;
;width: 100%" type="text" class="gift-input"></div></section><section style="flex-wrap: wrap;
position: relative;
padding: 1rem 1rem;
margin-bottom: 0.5rem;
background-color: hsl(0, 0%, 100%);
border: solid hsl(0, 0%, 100%) 2px;
border-radius: 6px;
box-shadow: 0px 1px 3px hsl(0, 0%, 74%);">
<div style="display: flex;
font-weight: bold;">
<span>
<span style="color: #5271FF;"><em>(Sans titre)</em></span>
</span>
<span style="margin-left: auto;
padding-left: 0.75rem;
flex: none;
margin-bottom: 10px;">
<span style="box-shadow: 0px 1px 3px hsl(0, 0%, 74%);
padding-left: 0.7rem;
padding-right: 0.7rem;
padding-top: 0.4rem;
padding-bottom: 0.4rem;
border-radius: 4px;
background-color: hsl(0, 0%, 100%);
color: hsl(180, 15%, 41%);">Numérique</span>
</span>
</div>
<div style="display: flex;">
<span>
<p class="present-question-stem" style="color: hsl(0, 0%, 0%);">
Quand est né Ulysses S. Grant&nbsp;?
</p>
</span>
</div>
<div><p style="color: hsl(0, 0%, 0%);">Réponse: </p><input placeholder="" style="display: inline-block;
padding: 0.375rem 0.75rem;
line-height: 1.5;
color: hsl(0, 0%, 16%);
background-color: hsl(0, 0%, 100%);
border: 1px solid hsl(0, 0%, 81%);
border-radius: 0.25rem;
box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
width: 90%;
;width: 100%" type="text" class="gift-input"><p style="color: hsl(0, 0%, 0%);">Réponse: </p><input placeholder="" style="display: inline-block;
padding: 0.375rem 0.75rem;
line-height: 1.5;
color: hsl(0, 0%, 16%);
background-color: hsl(0, 0%, 100%);
border: 1px solid hsl(0, 0%, 81%);
border-radius: 0.25rem;
box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
width: 90%;
;width: 100%" type="text" class="gift-input"></div></section><section style="flex-wrap: wrap;
position: relative;
padding: 1rem 1rem;
margin-bottom: 0.5rem;
background-color: hsl(0, 0%, 100%);
border: solid hsl(0, 0%, 100%) 2px;
border-radius: 6px;
box-shadow: 0px 1px 3px hsl(0, 0%, 74%);">
<div style="display: flex;
font-weight: bold;">
<span>
<span style="color: #5271FF;"><em>(Sans titre)</em></span>
</span>
<span style="margin-left: auto;
padding-left: 0.75rem;
flex: none;
margin-bottom: 10px;">
<span style="box-shadow: 0px 1px 3px hsl(0, 0%, 74%);
padding-left: 0.7rem;
padding-right: 0.7rem;
padding-top: 0.4rem;
padding-bottom: 0.4rem;
border-radius: 4px;
background-color: hsl(0, 0%, 100%);
color: hsl(180, 15%, 41%);">Vrai/Faux</span>
</span>
</div>
<div style="display: flex;">
<span>
<p class="present-question-stem" style="color: hsl(0, 0%, 0%);">
2+2 = 4 ?
</p>
</span>
</div>
<span style="color: hsl(0, 0%, 0%);">Choisir une réponse:</span>
<div class="multiple-choice-answers-container">
<input name="idOrgRFcP7" id="id0Y3L_s" type="radio" class="gift-input">
<label for="id0Y3L_s" style="display: inline-block;
padding: 0.2em 0 0.2em 0;
color: hsl(0, 0%, 0%);">
Vrai
</label>
</div>
<div class="multiple-choice-answers-container">
<input name="idOrgRFcP7" id="idYvbRmI" type="radio" class="gift-input">
<label for="idYvbRmI" style="display: inline-block;
padding: 0.2em 0 0.2em 0;
color: hsl(0, 0%, 0%);">
Faux
</label>
</div>
</section><section style="flex-wrap: wrap;
position: relative;
padding: 1rem 1rem;
margin-bottom: 0.5rem;
background-color: hsl(0, 0%, 100%);
border: solid hsl(0, 0%, 100%) 2px;
border-radius: 6px;
box-shadow: 0px 1px 3px hsl(0, 0%, 74%);">
<div style="display: flex;
font-weight: bold;">
<span>
<span style="color: #5271FF;"><em>(Sans titre)</em></span>
</span>
<span style="margin-left: auto;
padding-left: 0.75rem;
flex: none;
margin-bottom: 10px;">
<span style="box-shadow: 0px 1px 3px hsl(0, 0%, 74%);
padding-left: 0.7rem;
padding-right: 0.7rem;
padding-top: 0.4rem;
padding-bottom: 0.4rem;
border-radius: 4px;
background-color: hsl(0, 0%, 100%);
color: hsl(180, 15%, 41%);">Choix multiple</span>
</span>
</div>
<div style="display: flex;">
<span>
<p class="present-question-stem" style="color: hsl(0, 0%, 0%);">
Quelles villes trouve-t-on au Canada?
</p>
</span>
</div>
<span style="color: hsl(0, 0%, 0%);">Choisir une réponse ou plusieurs:</span>
<div class="multiple-choice-answers-container">
<input name="idIWtFb7BZ" id="idgi6jMo" type="checkbox" class="gift-input">
<label for="idgi6jMo" style="display: inline-block;
padding: 0.2em 0 0.2em 0;
color: hsl(0, 0%, 0%);">
Montréal
</label>
</div>
<div class="multiple-choice-answers-container">
<input name="idIWtFb7BZ" id="idmHcWKn" type="checkbox" class="gift-input">
<label for="idmHcWKn" style="display: inline-block;
padding: 0.2em 0 0.2em 0;
color: hsl(0, 0%, 0%);">
Ottawa
</label>
</div>
<div class="multiple-choice-answers-container">
<input name="idIWtFb7BZ" id="idSmdZmZ" type="checkbox" class="gift-input">
<label for="idSmdZmZ" style="display: inline-block;
padding: 0.2em 0 0.2em 0;
color: hsl(0, 0%, 0%);">
Vancouver
</label>
</div>
<div class="multiple-choice-answers-container">
<input name="idIWtFb7BZ" id="idOwpWf3" type="checkbox" class="gift-input">
<label for="idOwpWf3" style="display: inline-block;
padding: 0.2em 0 0.2em 0;
color: hsl(0, 0%, 0%);">
New York
</label>
</div>
<div class="multiple-choice-answers-container">
<input name="idIWtFb7BZ" id="idT8VlUn" type="checkbox" class="gift-input">
<label for="idT8VlUn" style="display: inline-block;
padding: 0.2em 0 0.2em 0;
color: hsl(0, 0%, 0%);">
Paris
</label>
</div>
<div style="position: relative;
margin-top: 1rem;
padding: 0 1rem;
background-color: hsl(43, 100%, 94%);
color: hsl(43, 95%, 9%);
border: hsl(36, 84%, 93%) 1px solid;
border-radius: 6px;
box-shadow: 0px 2px 5px hsl(0, 0%, 74%);">
</div></section>

6
package-lock.json generated Normal file
View file

@ -0,0 +1,6 @@
{
"name": "EvalueTonSavoir",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}