1- import { DOCXExporter } from '@blocknote/xl-docx-exporter' ;
2- import { ODTExporter } from '@blocknote/xl-odt-exporter' ;
3- import { PDFExporter } from '@blocknote/xl-pdf-exporter' ;
41import {
52 Button ,
63 Loader ,
@@ -10,30 +7,19 @@ import {
107 VariantType ,
118 useToastProvider ,
129} from '@gouvfr-lasuite/cunningham-react' ;
13- import { DocumentProps , pdf } from '@react-pdf/renderer' ;
14- import jsonemoji from 'emoji-datasource-apple' with { type : 'json' } ;
1510import i18next from 'i18next' ;
1611import JSZip from 'jszip' ;
17- import {
18- cloneElement ,
19- isValidElement ,
20- useEffect ,
21- useRef ,
22- useState ,
23- } from 'react' ;
12+ import { useEffect , useMemo , useRef , useState } from 'react' ;
2413import { useTranslation } from 'react-i18next' ;
2514import { css } from 'styled-components' ;
2615
2716import { Box , ButtonCloseModal , Text } from '@/components' ;
2817import { useMediaUrl } from '@/core' ;
2918import { useEditorStore } from '@/docs/doc-editor/stores/useEditorStore' ;
30- import { Doc , useTrans } from '@/docs/doc-management' ;
19+ import { type Doc , useTrans } from '@/docs/doc-management' ;
3120import { fallbackLng } from '@/i18n/config' ;
3221
33- import { exportCorsResolveFileUrl } from '../api/exportResolveFileUrl' ;
34- import { docxDocsSchemaMappings } from '../mappingDocx' ;
35- import { odtDocsSchemaMappings } from '../mappingODT' ;
36- import { pdfDocsSchemaMappings } from '../mappingPDF' ;
22+ import ModulesExport from '../hooks/' ;
3723import { downloadFile } from '../utils' ;
3824import {
3925 addMediaFilesToZip ,
@@ -42,13 +28,7 @@ import {
4228} from '../utils_html' ;
4329import { printDocumentWithStyles } from '../utils_print' ;
4430
45- enum DocDownloadFormat {
46- HTML = 'html' ,
47- PDF = 'pdf' ,
48- DOCX = 'docx' ,
49- ODT = 'odt' ,
50- PRINT = 'print' ,
51- }
31+ const useExportAGPL = ModulesExport ?. useExportAGPL ;
5232
5333interface ModalExportProps {
5434 onClose : ( ) => void ;
@@ -60,12 +40,13 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
6040 const { toast } = useToastProvider ( ) ;
6141 const { editor } = useEditorStore ( ) ;
6242 const [ isExporting , setIsExporting ] = useState ( false ) ;
63- const [ format , setFormat ] = useState < DocDownloadFormat > (
64- DocDownloadFormat . PDF ,
65- ) ;
6643 const { untitledDocument } = useTrans ( ) ;
6744 const mediaUrl = useMediaUrl ( ) ;
6845 const selectRef = useRef < HTMLDivElement > ( null ) ;
46+ const exportAGPL = useExportAGPL ?.( doc , editor ) ;
47+ const [ format , setFormat ] = useState (
48+ exportAGPL ?. formats . find ( ( opt ) => opt . value === 'pdf' ) ?. value || 'html' ,
49+ ) ;
6950
7051 useEffect ( ( ) => {
7152 const frameId = requestAnimationFrame ( ( ) => {
@@ -77,22 +58,18 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
7758 return ( ) => cancelAnimationFrame ( frameId ) ;
7859 } , [ ] ) ;
7960
80- const formatOptions = [
81- { label : t ( 'PDF' ) , value : DocDownloadFormat . PDF } ,
82- { label : t ( 'Docx' ) , value : DocDownloadFormat . DOCX } ,
83- { label : t ( 'ODT' ) , value : DocDownloadFormat . ODT } ,
84- { label : t ( 'HTML' ) , value : DocDownloadFormat . HTML } ,
85- { label : t ( 'Print' ) , value : DocDownloadFormat . PRINT } ,
86- ] ;
61+ const formatSelect = useMemo ( ( ) => {
62+ const formatOptions = ( exportAGPL ?. formats || [ ] ) . concat ( [
63+ { label : t ( 'HTML' ) , value : 'html' } ,
64+ { label : t ( 'Print' ) , value : 'print' } ,
65+ ] ) ;
8766
88- const formatLabels = Object . fromEntries (
89- formatOptions . map ( ( opt ) => [ opt . value , opt . label ] ) ,
90- ) ;
67+ const formatLabels = Object . fromEntries (
68+ formatOptions . map ( ( opt ) => [ opt . value , opt . label ] ) ,
69+ ) ;
9170
92- const downloadButtonAriaLabel =
93- format === DocDownloadFormat . PRINT
94- ? t ( 'Print' )
95- : t ( 'Download {{format}}' , { format : formatLabels [ format ] } ) ;
71+ return { formatOptions, formatLabels } ;
72+ } , [ t , exportAGPL ?. formats ] ) ;
9673
9774 async function onSubmit ( ) {
9875 if ( ! editor ) {
@@ -103,7 +80,7 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
10380 setIsExporting ( true ) ;
10481
10582 // Handle print separately as it doesn't download a file
106- if ( format === DocDownloadFormat . PRINT ) {
83+ if ( format === 'print' ) {
10784 printDocumentWithStyles ( ) ;
10885 setIsExporting ( false ) ;
10986 onClose ( ) ;
@@ -118,62 +95,9 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
11895
11996 const documentTitle = doc . title || untitledDocument ;
12097
121- const exportDocument = editor . document ;
122- let blobExport : Blob ;
123- if ( format === DocDownloadFormat . PDF ) {
124- const exporter = new PDFExporter ( editor . schema , pdfDocsSchemaMappings , {
125- resolveFileUrl : async ( url ) => exportCorsResolveFileUrl ( doc . id , url ) ,
126- emojiSource : {
127- format : 'png' ,
128- builder ( code ) {
129- const emojisFound = jsonemoji . filter (
130- ( e ) =>
131- e . unified . split ( '-' ) [ 0 ] . toLowerCase ( ) ===
132- code . split ( '-' ) [ 0 ] . toLowerCase ( ) ,
133- ) ;
134-
135- const emoji = emojisFound . find ( ( e ) =>
136- e . unified . toLocaleLowerCase ( ) . includes ( code . toLowerCase ( ) ) ,
137- ) ;
138-
139- if ( emoji ) {
140- return `/assets/fonts/emoji/${ emoji . image } ` ;
141- }
98+ let blobExport = await exportAGPL ?. docToBlob ( format , documentTitle ) ;
14299
143- return '/assets/fonts/emoji/fallback.png' ;
144- } ,
145- } ,
146- } ) ;
147- const rawPdfDocument = ( await exporter . toReactPDFDocument (
148- exportDocument ,
149- ) ) as React . ReactElement < DocumentProps > ;
150-
151- // Add language, title and outline properties to improve PDF accessibility and navigation
152- const pdfDocument = isValidElement ( rawPdfDocument )
153- ? cloneElement ( rawPdfDocument , {
154- language : i18next . language ,
155- title : documentTitle ,
156- pageMode : 'useOutlines' ,
157- } )
158- : rawPdfDocument ;
159-
160- blobExport = await pdf ( pdfDocument ) . toBlob ( ) ;
161- } else if ( format === DocDownloadFormat . DOCX ) {
162- const exporter = new DOCXExporter ( editor . schema , docxDocsSchemaMappings , {
163- resolveFileUrl : async ( url ) => exportCorsResolveFileUrl ( doc . id , url ) ,
164- } ) ;
165-
166- blobExport = await exporter . toBlob ( exportDocument , {
167- documentOptions : { title : documentTitle } ,
168- sectionOptions : { } ,
169- } ) ;
170- } else if ( format === DocDownloadFormat . ODT ) {
171- const exporter = new ODTExporter ( editor . schema , odtDocsSchemaMappings , {
172- resolveFileUrl : async ( url ) => exportCorsResolveFileUrl ( doc . id , url ) ,
173- } ) ;
174-
175- blobExport = await exporter . toODTDocument ( exportDocument ) ;
176- } else if ( format === DocDownloadFormat . HTML ) {
100+ if ( ! blobExport && format === 'html' ) {
177101 // Use BlockNote "full HTML" export so that we stay closer to the editor rendering.
178102 const fullHtml = await editor . blocksToFullHTML ( ) ;
179103
@@ -206,14 +130,15 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
206130 zip . file ( 'styles.css' , cssContent ) ;
207131
208132 blobExport = await zip . generateAsync ( { type : 'blob' } ) ;
209- } else {
133+ }
134+
135+ if ( ! blobExport ) {
210136 toast ( t ( 'The export failed' ) , VariantType . ERROR ) ;
211137 setIsExporting ( false ) ;
212138 return ;
213139 }
214140
215- const downloadExtension =
216- format === DocDownloadFormat . HTML ? 'zip' : format ;
141+ const downloadExtension = format === 'html' ? 'zip' : format ;
217142
218143 downloadFile ( blobExport , `${ filename } .${ downloadExtension } ` ) ;
219144
@@ -250,13 +175,19 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
250175 </ Button >
251176 < Button
252177 data-testid = "doc-export-download-button"
253- aria-label = { downloadButtonAriaLabel }
178+ aria-label = {
179+ format === 'print'
180+ ? t ( 'Print' )
181+ : t ( 'Download {{format}}' , {
182+ format : formatSelect . formatLabels [ format ] ,
183+ } )
184+ }
254185 variant = "primary"
255186 fullWidth
256187 onClick = { ( ) => void onSubmit ( ) }
257188 disabled = { isExporting }
258189 >
259- { format === DocDownloadFormat . PRINT ? t ( 'Print' ) : t ( 'Download' ) }
190+ { format === 'print' ? t ( 'Print' ) : t ( 'Download' ) }
260191 </ Button >
261192 </ >
262193 }
@@ -303,11 +234,9 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
303234 clearable = { false }
304235 fullWidth
305236 label = { t ( 'Format' ) }
306- options = { formatOptions }
237+ options = { formatSelect . formatOptions }
307238 value = { format }
308- onChange = { ( options ) =>
309- setFormat ( options . target . value as DocDownloadFormat )
310- }
239+ onChange = { ( options ) => setFormat ( options . target . value as string ) }
311240 />
312241 </ Box >
313242
0 commit comments