1+ import { SpecificBlock } from '@blocknote/core' ;
12import { DOCXExporter } from '@blocknote/xl-docx-exporter' ;
23import { ODTExporter } from '@blocknote/xl-odt-exporter' ;
34import { PDFExporter } from '@blocknote/xl-pdf-exporter' ;
45import {
56 Button ,
7+ Checkbox ,
68 Loader ,
79 Modal ,
810 ModalSize ,
@@ -20,9 +22,15 @@ import { css } from 'styled-components';
2022
2123import { Box , ButtonCloseModal , Text } from '@/components' ;
2224import { useMediaUrl } from '@/core' ;
23- import { useEditorStore } from '@/docs/doc-editor' ;
25+ import {
26+ DocsBlockSchema ,
27+ DocsInlineContentSchema ,
28+ DocsStyleSchema ,
29+ useEditorStore ,
30+ } from '@/docs/doc-editor' ;
2431import { Doc , useTrans } from '@/docs/doc-management' ;
2532import { fallbackLng } from '@/i18n/config' ;
33+ import { safeLocalStorage } from '@/utils/storages' ;
2634
2735import { exportCorsResolveFileUrl } from '../api/exportResolveFileUrl' ;
2836import { docxDocsSchemaMappings } from '../mappingDocx' ;
@@ -57,6 +65,18 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
5765 const [ format , setFormat ] = useState < DocDownloadFormat > (
5866 DocDownloadFormat . PDF ,
5967 ) ;
68+ const documentHasH1 =
69+ editor ?. document . some (
70+ ( block ) => block . type === 'heading' && block . props . level === 1 ,
71+ ) ?? false ;
72+
73+ const [ withTitle , setWithTitle ] = useState ( ( ) => {
74+ const stored = safeLocalStorage . getItem ( `export-with-title-${ doc . id } ` ) ;
75+ if ( stored === null ) {
76+ return ! documentHasH1 ;
77+ }
78+ return stored !== 'false' ;
79+ } ) ;
6080 const { untitledDocument } = useTrans ( ) ;
6181 const mediaUrl = useMediaUrl ( ) ;
6282
@@ -84,7 +104,27 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
84104
85105 const documentTitle = doc . title || untitledDocument ;
86106
87- const exportDocument = editor . document ;
107+ const titleBlock : SpecificBlock <
108+ DocsBlockSchema ,
109+ 'heading' ,
110+ DocsInlineContentSchema ,
111+ DocsStyleSchema
112+ > = {
113+ id : crypto . randomUUID ( ) ,
114+ type : 'heading' ,
115+ props : {
116+ level : 1 ,
117+ textColor : 'default' ,
118+ backgroundColor : 'default' ,
119+ textAlignment : 'left' ,
120+ } ,
121+ content : [ { type : 'text' , text : documentTitle , styles : { } } ] ,
122+ children : [ ] ,
123+ } ;
124+
125+ const exportDocument = withTitle
126+ ? [ titleBlock , ...editor . document ]
127+ : editor . document ;
88128 let blobExport : Blob ;
89129 if ( format === DocDownloadFormat . PDF ) {
90130 const exporter = new PDFExporter ( editor . schema , pdfDocsSchemaMappings , {
@@ -135,15 +175,15 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
135175 blobExport = await exporter . toODTDocument ( exportDocument ) ;
136176 } else if ( format === DocDownloadFormat . HTML ) {
137177 // Use BlockNote "full HTML" export so that we stay closer to the editor rendering.
138- const fullHtml = await editor . blocksToFullHTML ( ) ;
178+ const fullHtml = await editor . blocksToFullHTML ( exportDocument ) ;
139179
140180 // Parse HTML and fetch media so that we can package a fully offline HTML document in a ZIP.
141181 const domParser = new DOMParser ( ) ;
142182 const parsedDocument = domParser . parseFromString ( fullHtml , 'text/html' ) ;
143183
144184 const zip = new JSZip ( ) ;
145185
146- improveHtmlAccessibility ( parsedDocument , documentTitle ) ;
186+ improveHtmlAccessibility ( parsedDocument ) ;
147187 await addMediaFilesToZip ( parsedDocument , zip , mediaUrl ) ;
148188
149189 const lang = i18next . language || fallbackLng ;
@@ -273,6 +313,20 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
273313 }
274314 />
275315
316+ { format !== DocDownloadFormat . PRINT && (
317+ < Checkbox
318+ label = { t ( 'Include document title' ) }
319+ checked = { withTitle }
320+ onChange = { ( e ) => {
321+ setWithTitle ( e . target . checked ) ;
322+ safeLocalStorage . setItem (
323+ `export-with-title-${ doc . id } ` ,
324+ String ( e . target . checked ) ,
325+ ) ;
326+ } }
327+ />
328+ ) }
329+
276330 { isExporting && (
277331 < Box
278332 $align = "center"
0 commit comments