From 0805216cc6dd3bb5f2c9fb9cb0a51ee9916c636f Mon Sep 17 00:00:00 2001 From: Cyril Date: Mon, 1 Dec 2025 15:16:41 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20added=20accessible=20html?= =?UTF-8?q?=20export=20and=20moved=20download=20option?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit replaced “copy as html” with export modal option and full media zip export Signed-off-by: Cyril --- .../doc-export/components/ModalExport.tsx | 58 ++++++----------- .../src/features/docs/doc-export/utils.ts | 62 +++++++++++++++++++ 2 files changed, 81 insertions(+), 39 deletions(-) diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/components/ModalExport.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/components/ModalExport.tsx index 51d95a4f..df994c54 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-export/components/ModalExport.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-export/components/ModalExport.tsx @@ -13,13 +13,16 @@ import { import { DocumentProps, pdf } from '@react-pdf/renderer'; import jsonemoji from 'emoji-datasource-apple' assert { type: 'json' }; import i18next from 'i18next'; +import JSZip from 'jszip'; import { cloneElement, isValidElement, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; import { Box, ButtonCloseModal, Text } from '@/components'; +import { useMediaUrl } from '@/core'; import { useEditorStore } from '@/docs/doc-editor'; import { Doc, useTrans } from '@/docs/doc-management'; +import { fallbackLng } from '@/i18n/config'; import { exportCorsResolveFileUrl } from '../api/exportResolveFileUrl'; import { TemplatesOrdering, useTemplates } from '../api/useTemplates'; @@ -27,7 +30,7 @@ import { docxDocsSchemaMappings } from '../mappingDocx'; import { odtDocsSchemaMappings } from '../mappingODT'; import { pdfDocsSchemaMappings } from '../mappingPDF'; import { - deriveMediaFilename, + addMediaFilesToZip, downloadFile, generateHtmlDocument, } from '../utils'; @@ -57,6 +60,7 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => { DocDownloadFormat.PDF, ); const { untitledDocument } = useTrans(); + const mediaUrl = useMediaUrl(); const templateOptions = useMemo(() => { const templateOptions = (templates?.pages || []) @@ -155,41 +159,12 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => { const domParser = new DOMParser(); const parsedDocument = domParser.parseFromString(fullHtml, 'text/html'); - const mediaFiles: { filename: string; blob: Blob }[] = []; - const mediaElements = Array.from( - parsedDocument.querySelectorAll< - | HTMLImageElement - | HTMLVideoElement - | HTMLAudioElement - | HTMLSourceElement - >('img, video, audio, source'), - ); + const zip = new JSZip(); - await Promise.all( - mediaElements.map(async (element, index) => { - const src = element.getAttribute('src'); + await addMediaFilesToZip(parsedDocument, zip, mediaUrl); - if (!src) { - return; - } - - const fetched = await exportCorsResolveFileUrl(doc.id, src); - - if (!(fetched instanceof Blob)) { - return; - } - - const filename = deriveMediaFilename({ - src, - index, - blob: fetched, - }); - element.setAttribute('src', filename); - mediaFiles.push({ filename, blob: fetched }); - }), - ); - - const lang = i18next.language || 'fr'; + const lang = i18next.language || fallbackLng; + const editorHtmlWithLocalMedia = parsedDocument.body.innerHTML; const htmlContent = generateHtmlDocument( documentTitle, @@ -197,16 +172,19 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => { lang, ); - blobExport = new Blob([htmlContent], { - type: 'text/html;charset=utf-8', - }); + zip.file('index.html', htmlContent); + + blobExport = await zip.generateAsync({ type: 'blob' }); } else { toast(t('The export failed'), VariantType.ERROR); setIsExporting(false); return; } - downloadFile(blobExport, `${filename}.${format}`); + const downloadExtension = + format === DocDownloadFormat.HTML ? 'zip' : format; + + downloadFile(blobExport, `${filename}.${downloadExtension}`); toast( t('Your {{format}} was downloaded succesfully', { @@ -283,7 +261,9 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => { className="--docs--modal-export-content" > - {t('Download your document in a .docx, .odt or .pdf format.')} + {t( + 'Download your document in a .docx, .odt, .pdf or .html(zip) format.', + )}