@@ -19,12 +19,23 @@ import React from 'react'
1919import remarkEmoji from 'remark-emoji'
2020import rehypeReact from 'rehype-react'
2121import CodeFence from '../components/atoms/markdown/CodeFence'
22- import { getGlobalCss , selectTheme } from './styled/styleUtil'
22+ import { getGlobalCss , selectTheme } from './styled/styleUtil'
2323
2424const sanitizeNoteName = function ( rawNoteName : string ) : string {
2525 return filenamify ( rawNoteName . toLowerCase ( ) . replace ( / \s + / g, '-' ) )
2626}
2727
28+ const getFrontMatter = ( note : NoteDoc ) : string => {
29+ return [
30+ '---' ,
31+ `title: "${ note . title } "` ,
32+ `tags: "${ note . tags . join ( ) } "` ,
33+ '---' ,
34+ '' ,
35+ '' ,
36+ ] . join ( '\n' )
37+ }
38+
2839const sanitizeSchema = mergeDeepRight ( gh , {
2940 attributes : { '*' : [ 'className' ] } ,
3041} )
@@ -33,7 +44,7 @@ export const exportNoteAsHtmlFile = async (
3344 note : NoteDoc ,
3445 preferences : Preferences ,
3546 pushMessage : ( context : any ) => any ,
36- previewStyle ?: string ,
47+ previewStyle ?: string
3748) : Promise < void > => {
3849 await unified ( )
3950 . use ( remarkParse )
@@ -88,18 +99,7 @@ export const exportNoteAsMarkdownFile = async (
8899 return
89100 }
90101 let content = file . toString ( ) . trim ( ) + '\n'
91- if ( includeFrontMatter ) {
92- content =
93- [
94- '---' ,
95- `title: "${ note . title } "` ,
96- `tags: "${ note . tags . join ( ) } "` ,
97- '---' ,
98- '' ,
99- '' ,
100- ] . join ( '\n' ) + content
101- }
102-
102+ content += includeFrontMatter ? getFrontMatter ( note ) : ''
103103 downloadString (
104104 content ,
105105 `${ sanitizeNoteName ( note . title ) } .md` ,
@@ -118,120 +118,180 @@ const schema = mergeDeepRight(gh, {
118118 } ,
119119} )
120120
121- const getCssLinks = ( preferences : Preferences ) => {
122- let cssHrefs : string [ ] = [ ]
123- const app = window . require ( 'electron' ) . remote . app ;
121+ const fetchCorrectMdThemeName = ( theme : string ) => {
122+ return theme === 'solarized-dark' ? 'solarized' : theme
123+ }
124+
125+ const getCssLinks = ( preferences : Preferences ) => {
126+ const cssHrefs : string [ ] = [ ]
127+ const app = window . require ( 'electron' ) . remote . app
124128 const isProd = app . isPackaged
125- const parentPathTheme = app . getAppPath ( ) + ( ( isProd === true ) ? "/compiled/app" : "/../node_modules" )
126- let editorTheme = preferences [ 'editor.theme' ]
127- let markdownCodeBlockTheme = preferences [ 'markdown.codeBlockTheme' ]
128- if ( editorTheme === 'solarized-dark' ) {
129- editorTheme = 'solarized'
130- }
129+ const pathPrefix = 'file://' + app . getAppPath ( )
130+ const parentPathTheme =
131+ pathPrefix + ( isProd === true ? '/compiled/app' : '/../node_modules' )
132+ const editorTheme = fetchCorrectMdThemeName ( preferences [ 'editor.theme' ] )
133+ const markdownCodeBlockTheme = fetchCorrectMdThemeName (
134+ preferences [ 'markdown.codeBlockTheme' ]
135+ )
136+
131137 const editorThemePath = `${ parentPathTheme } /codemirror/theme/${ editorTheme } .css`
132138 cssHrefs . push ( editorThemePath )
133139 if ( editorTheme !== markdownCodeBlockTheme ) {
134140 if ( markdownCodeBlockTheme ) {
135- if ( markdownCodeBlockTheme === 'solarized-dark' ) {
136- markdownCodeBlockTheme = 'solarized'
137- }
138141 const markdownCodeBlockThemePath = `${ parentPathTheme } /codemirror/theme/${ markdownCodeBlockTheme } .css`
139142 cssHrefs . push ( markdownCodeBlockThemePath )
140143 }
141144 }
142145 return cssHrefs
143146}
144147
148+ const cssStyleLinkGenerator = ( href : string ) =>
149+ `<link rel="stylesheet" href="${ href } " type="text/css"/>`
150+
151+ const getPrintStyle = ( ) => `
152+ <style media="print">
153+ pre code {
154+ white-space: pre-wrap;
155+ }
156+ </style>
157+ `
158+
159+ const generatePrintToPdfHTML = (
160+ markdownHTML : string | Uint8Array ,
161+ preferences : Preferences ,
162+ previewStyle ?: string
163+ ) => {
164+ const cssHrefs : string [ ] = getCssLinks ( preferences )
165+ const generalThemeName = preferences [ 'general.theme' ]
166+ const cssLinks = cssHrefs
167+ . map ( ( href ) => cssStyleLinkGenerator ( href ) )
168+ . join ( '\n' )
169+ const appThemeCss = getGlobalCss ( selectTheme ( generalThemeName ) )
170+ const previewStyleCssEl = previewStyle ? `<style>${ previewStyle } </style>` : ''
171+ const appThemeCssEl = appThemeCss ? `<style>${ appThemeCss } </style>` : ''
172+
173+ return `<!DOCTYPE html>
174+ <html lang="en">
175+ <head>
176+ <meta charset="UTF-8" />
177+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
178+ <!-- Preview styles -->
179+ ${ appThemeCssEl }
180+ ${ previewStyleCssEl }
181+
182+ <!-- Link tag styles -->
183+ ${ cssStyleLinkGenerator (
184+ 'https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.css'
185+ ) }
186+ ${ cssLinks }
187+
188+ <!-- Print Styles -->
189+ ${ getPrintStyle ( ) }
190+ </head>
191+ <body>
192+ <div class="${ generalThemeName } ">
193+ ${ markdownHTML }
194+ </div>
195+ </body>
196+ </html>
197+ `
198+ }
199+
145200export const exportNoteAsPdfFile = async (
146201 note : NoteDoc ,
147202 preferences : Preferences ,
148203 pushMessage : ( context : any ) => any ,
149- previewStyle ?: string ,
204+ { includeFrontMatter } : { includeFrontMatter : boolean } ,
205+ previewStyle ?: string
150206) : Promise < void > => {
151207 await unified ( )
152208 . use ( remarkParse )
153- . use ( remarkStringify )
209+ . use ( remarkEmoji , { emoticon : false } )
210+ . use ( [ remarkRehype , { allowDangerousHTML : true } ] )
211+ . use ( rehypeRaw )
212+ . use ( rehypeSanitize , schema )
213+ . use ( remarkMath )
214+ . use ( rehypeCodeMirror , {
215+ ignoreMissing : true ,
216+ theme : preferences [ 'markdown.codeBlockTheme' ] ,
217+ } )
218+ . use ( rehypeKatex , { output : 'htmlAndMathml' } )
219+ . use ( rehypeReact , {
220+ createElement : React . createElement ,
221+ components : {
222+ pre : CodeFence ,
223+ } ,
224+ } )
225+ . use ( rehypeStringify )
154226 . process ( note . content , ( err , file ) => {
155227 if ( err != null ) {
156228 pushMessage ( {
157229 title : 'Note processing failed' ,
158230 description : 'Please check markdown syntax and try again later.' ,
159231 } )
232+ return
160233 }
161- let content = file . toString ( ) . trim ( ) + '\n'
162- const markdownProcessor = unified ( )
163- . use ( remarkParse )
164- . use ( remarkEmoji , { emoticon : false } )
165- . use ( [ remarkRehype , { allowDangerousHTML : true } ] )
166- . use ( rehypeRaw )
167- . use ( rehypeSanitize , schema )
168- . use ( remarkMath )
169- . use ( rehypeCodeMirror , {
170- ignoreMissing : true ,
171- theme : preferences [ 'markdown.codeBlockTheme' ] ,
172- } )
173- . use ( rehypeKatex )
174- . use ( rehypeReact , {
175- createElement : React . createElement ,
176- components : {
177- pre : CodeFence ,
178- } ,
179- } )
180- . use ( rehypeStringify )
181-
182- // Process note-markdown content into react string
183- let resultObj = markdownProcessor . processSync ( content )
184234
185- // Create new window (hidden)
235+ const stringifiedMdContent = file . toString ( ) . trim ( ) + '\n'
186236 const { BrowserWindow } = window . require ( 'electron' ) . remote
187- const app = window . require ( 'electron' ) . remote . app ;
188- const ipcMain = window . require ( 'electron' ) . remote . ipcMain ;
189- const isProd = app . isPackaged === true
190- const parentPathHTML = app . getAppPath ( ) + ( ( isProd === true ) ? "/compiled/app/static" : "/../static" )
191237 const windowOptions = {
192- webPreferences : { nodeIntegration : true , webSecurity : false } ,
193- show : false
238+ webPreferences : {
239+ nodeIntegration : true ,
240+ webSecurity : false ,
241+ javascript : false ,
242+ } ,
243+ show : false ,
194244 }
195245 const win = new BrowserWindow ( windowOptions )
196-
197- // Load HTML for rendering react string for markdown content created earlier
198- win . loadFile ( `${ parentPathHTML } /render_md_to_pdf.html` )
246+ const htmlStr = generatePrintToPdfHTML (
247+ stringifiedMdContent ,
248+ preferences ,
249+ previewStyle
250+ )
251+ const encodedStr = encodeURIComponent ( htmlStr )
252+ win . loadURL ( 'data:text/html;charset=UTF-8,' + encodedStr )
199253 win . webContents . on ( 'did-finish-load' , function ( ) {
200- // Fetch needed CSS styles
201- const generalThemeName = preferences [ 'general.theme' ]
202- const appThemeCss = getGlobalCss ( selectTheme ( generalThemeName ) )
203- let cssHrefs : string [ ] = getCssLinks ( preferences )
204- if ( previewStyle ) {
205- win . webContents . insertCSS ( previewStyle )
206- win . webContents . insertCSS ( appThemeCss )
254+ // Enable when newer version of electron is available
255+ const tagsStr =
256+ note . tags . length > 0 ? `, tags: [${ note . tags . join ( ' ' ) } ]` : ''
257+ const headerFooter : Record < string , string > = {
258+ title : `${ note . title } ${ tagsStr } ` ,
259+ url : `file://${ sanitizeNoteName ( note . title ) } .pdf` ,
207260 }
208- // Do not show the window while exporting (for debugging purposes only)
209- // win.show()
210- setTimeout ( ( ) => {
211- // Send message to window to render the markdown content with the applied css and theme class
212- win . webContents . send ( 'render-markdown-to-pdf' , resultObj . contents , cssHrefs , generalThemeName )
213- } , 500 )
214- } )
215-
216- // When PDF rendered, notify me (doing this only once removes it for further messages)
217- // this way no need to remove it manually after receving the message
218- // another click on PDF export would once again bind the current note markdown to HTML rendered page
219- ipcMain . once ( 'pdf-notify-export-data' , ( _ : object , data : string , error : any ) => {
220- if ( data && ! error ) {
221- // We got the PDF offer user to save it
222- const pdfName = `${ sanitizeNoteName ( note . title ) } .pdf`
223- const pdfBlob = new Blob ( [ data ] , {
224- type : "application/pdf" // application/octet-stream
261+ const printOpts = {
262+ // Needed for codemirorr themes (backgrounds)
263+ printBackground : true ,
264+ // Enable margins if header footer is printed
265+ // No margins 1, default margins 0, 2 - minimum margins
266+ marginsType : includeFrontMatter ? 0 : 1 ,
267+ pageSize : 'A4' , // This could be chosen by user,
268+ headerFooter : includeFrontMatter ? headerFooter : undefined ,
269+ }
270+ win . webContents
271+ . printToPDF ( printOpts )
272+ . then ( ( data ) => {
273+ if ( data ) {
274+ // We got the PDF - offer the user to save it
275+ const pdfName = `${ sanitizeNoteName ( note . title ) } .pdf`
276+ const pdfBlob = new Blob ( [ data ] , {
277+ type : 'application/pdf' , // application/octet-stream
278+ } )
279+ downloadBlob ( pdfBlob , pdfName )
280+ } else {
281+ pushMessage ( {
282+ title : 'PDF export failed' ,
283+ description : 'Please try again later. Reason: Unknown' ,
284+ } )
285+ }
286+ // Destroy window (not shown but disposes it)
287+ win . destroy ( )
225288 } )
226- downloadBlob ( pdfBlob , pdfName )
227- } else {
228- pushMessage ( {
229- title : 'PDF export failed' ,
230- description : 'Please try again later.' + " Error: " + JSON . stringify ( error ) ,
289+ . catch ( ( err ) => {
290+ pushMessage ( {
291+ title : 'PDF export failed' ,
292+ description : 'Please try again later.' + ( err ? err : 'Unknown' ) ,
293+ } )
231294 } )
232- }
233- // Close window (it's hidden anyway, but dispose it)
234- win . close ( )
235295 } )
236296 return
237297 } )
0 commit comments