@@ -11,10 +11,19 @@ import rehypeKatex from 'rehype-katex'
11
11
import { mergeDeepRight } from 'ramda'
12
12
import gh from 'hast-util-sanitize/lib/github.json'
13
13
import { rehypeCodeMirror } from './../components/atoms/MarkdownPreviewer'
14
- import { downloadString } from './download'
14
+ import { downloadBlob , downloadString } from './download'
15
15
import { NoteDoc } from './db/types'
16
16
import { Preferences } from './preferences'
17
17
import { filenamify } from './string'
18
+ import React from 'react'
19
+ import remarkEmoji from 'remark-emoji'
20
+ import rehypeReact from 'rehype-react'
21
+ import CodeFence from '../components/atoms/markdown/CodeFence'
22
+ import { getGlobalCss , selectTheme } from './styled/styleUtil'
23
+
24
+ const sanitizeNoteName = function ( rawNoteName : string ) : string {
25
+ return filenamify ( rawNoteName . toLowerCase ( ) . replace ( / \s + / g, '-' ) )
26
+ }
18
27
19
28
const sanitizeSchema = mergeDeepRight ( gh , {
20
29
attributes : { '*' : [ 'className' ] } ,
@@ -23,7 +32,8 @@ const sanitizeSchema = mergeDeepRight(gh, {
23
32
export const exportNoteAsHtmlFile = async (
24
33
note : NoteDoc ,
25
34
preferences : Preferences ,
26
- previewStyle ?: string
35
+ pushMessage : ( context : any ) => any ,
36
+ previewStyle ?: string ,
27
37
) : Promise < void > => {
28
38
await unified ( )
29
39
. use ( remarkParse )
@@ -45,14 +55,16 @@ export const exportNoteAsHtmlFile = async (
45
55
. use ( rehypeKatex )
46
56
. process ( note . content , ( err , file ) => {
47
57
if ( err != null ) {
48
- /* TODO: Toast error */
49
- console . error ( err )
58
+ pushMessage ( {
59
+ title : 'Note processing failed' ,
60
+ description : 'Please check markdown syntax and try again later.' ,
61
+ } )
50
62
return
51
63
}
52
64
53
65
downloadString (
54
66
file . toString ( ) ,
55
- `${ filenamify ( note . title . toLowerCase ( ) . replace ( / \s + / g , '-' ) ) } .html` ,
67
+ `${ sanitizeNoteName ( note . title ) } .html` ,
56
68
'text/html'
57
69
)
58
70
return
@@ -61,15 +73,18 @@ export const exportNoteAsHtmlFile = async (
61
73
62
74
export const exportNoteAsMarkdownFile = async (
63
75
note : NoteDoc ,
76
+ pushMessage : ( context : any ) => any ,
64
77
{ includeFrontMatter } : { includeFrontMatter : boolean }
65
78
) : Promise < void > => {
66
79
await unified ( )
67
80
. use ( remarkParse )
68
81
. use ( remarkStringify )
69
82
. process ( note . content , ( err , file ) => {
70
83
if ( err != null ) {
71
- /* TODO: Toast error */
72
- console . error ( err )
84
+ pushMessage ( {
85
+ title : 'Note processing failed' ,
86
+ description : 'Please check markdown syntax and try again later.' ,
87
+ } )
73
88
return
74
89
}
75
90
let content = file . toString ( ) . trim ( ) + '\n'
@@ -87,10 +102,138 @@ export const exportNoteAsMarkdownFile = async (
87
102
88
103
downloadString (
89
104
content ,
90
- `${ filenamify ( note . title . toLowerCase ( ) . replace ( / \s + / g , '-' ) ) } .md` ,
105
+ `${ sanitizeNoteName ( note . title ) } .md` ,
91
106
'text/markdown'
92
107
)
93
108
return
94
109
} )
95
110
return
96
111
}
112
+
113
+ const schema = mergeDeepRight ( gh , {
114
+ attributes : {
115
+ '*' : [ ...gh . attributes [ '*' ] , 'className' , 'align' ] ,
116
+ input : [ ...gh . attributes . input , 'checked' ] ,
117
+ pre : [ 'dataRaw' ] ,
118
+ } ,
119
+ } )
120
+
121
+ const getCssLinks = ( preferences : Preferences ) => {
122
+ let cssHrefs : string [ ] = [ ]
123
+ const app = window . require ( 'electron' ) . remote . app ;
124
+ 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
+ }
131
+ const editorThemePath = `${ parentPathTheme } /codemirror/theme/${ editorTheme } .css`
132
+ cssHrefs . push ( editorThemePath )
133
+ if ( editorTheme !== markdownCodeBlockTheme ) {
134
+ if ( markdownCodeBlockTheme ) {
135
+ if ( markdownCodeBlockTheme === 'solarized-dark' ) {
136
+ markdownCodeBlockTheme = 'solarized'
137
+ }
138
+ const markdownCodeBlockThemePath = `${ parentPathTheme } /codemirror/theme/${ markdownCodeBlockTheme } .css`
139
+ cssHrefs . push ( markdownCodeBlockThemePath )
140
+ }
141
+ }
142
+ return cssHrefs
143
+ }
144
+
145
+ export const exportNoteAsPdfFile = async (
146
+ note : NoteDoc ,
147
+ preferences : Preferences ,
148
+ pushMessage : ( context : any ) => any ,
149
+ previewStyle ?: string ,
150
+ ) : Promise < void > => {
151
+ await unified ( )
152
+ . use ( remarkParse )
153
+ . use ( remarkStringify )
154
+ . process ( note . content , ( err , file ) => {
155
+ if ( err != null ) {
156
+ pushMessage ( {
157
+ title : 'Note processing failed' ,
158
+ description : 'Please check markdown syntax and try again later.' ,
159
+ } )
160
+ }
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 )
184
+
185
+ // Create new window (hidden)
186
+ 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" )
191
+ const windowOptions = {
192
+ webPreferences : { nodeIntegration : true , webSecurity : false } ,
193
+ show : false
194
+ }
195
+ 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` )
199
+ 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 )
207
+ }
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
225
+ } )
226
+ downloadBlob ( pdfBlob , pdfName )
227
+ } else {
228
+ pushMessage ( {
229
+ title : 'PDF export failed' ,
230
+ description : 'Please try again later.' + " Error: " + JSON . stringify ( error ) ,
231
+ } )
232
+ }
233
+ // Close window (it's hidden anyway, but dispose it)
234
+ win . close ( )
235
+ } )
236
+ return
237
+ } )
238
+ return
239
+ }
0 commit comments