diff --git a/ElectronClient/app/InteropServiceHelper.js b/ElectronClient/app/InteropServiceHelper.js index af0d788e8..91417327a 100644 --- a/ElectronClient/app/InteropServiceHelper.js +++ b/ElectronClient/app/InteropServiceHelper.js @@ -1,9 +1,88 @@ const { _ } = require('lib/locale'); const { bridge } = require('electron').remote.require('./bridge'); const InteropService = require('lib/services/InteropService'); +const Setting = require('lib/models/Setting'); +const md5 = require('md5'); +const url = require('url'); +const { shim } = require('lib/shim'); +// const { BrowserWindow } = require('electron'); class InteropServiceHelper { + static async exportNoteToHtmlFile(noteId) { + const tempFile = `${Setting.value('tempDir')}/${md5(Date.now() + Math.random())}.html`; + const exportOptions = {}; + exportOptions.path = tempFile; + exportOptions.format = 'html'; + exportOptions.target = 'file'; + exportOptions.sourceNoteIds = [noteId]; + + const service = new InteropService(); + + const result = await service.export(exportOptions); + console.info('Export HTML result: ', result); + return tempFile; + } + + static async exportNoteTo_(target, noteId, options = {}) { + let win = null; + let htmlFile = null; + + const cleanup = () => { + if (win) win.destroy(); + if (htmlFile) shim.fsDriver().remove(htmlFile); + }; + + try { + htmlFile = await this.exportNoteToHtmlFile(noteId); + + const windowOptions = { + show: true, + }; + + win = bridge().newBrowserWindow(windowOptions); + + return new Promise((resolve, reject) => { + win.webContents.on('did-finish-load', () => { + + if (target === 'pdf') { + win.webContents.printToPDF(options, (error, data) => { + cleanup(); + if (error) reject(error); + resolve(data); + }); + } else { + win.webContents.print(options, (success) => { + // TODO: This is correct but broken in Electron 4. Need to upgrade to 5+ + // It calls the callback right away with "false" even if the document hasn't be print yet. + + cleanup(); + if (!success) reject(new Error('Could not print')); + resolve(); + }); + } + }); + + win.loadURL(url.format({ + pathname: htmlFile, + protocol: 'file:', + slashes: true, + })); + }); + } catch (error) { + cleanup(); + throw error; + } + } + + static async exportNoteToPdf(noteId, options = {}) { + return this.exportNoteTo_('pdf', noteId, options); + } + + static async printNote(noteId, options = {}) { + return this.exportNoteTo_('printer', noteId, options); + } + static async export(dispatch, module, options = null) { if (!options) options = {}; diff --git a/ElectronClient/app/bridge.js b/ElectronClient/app/bridge.js index 4a42a61ea..92f62328b 100644 --- a/ElectronClient/app/bridge.js +++ b/ElectronClient/app/bridge.js @@ -1,5 +1,6 @@ const { _, setLocale } = require('lib/locale.js'); const { dirname } = require('lib/path-utils.js'); +const { BrowserWindow } = require('electron'); class Bridge { @@ -21,6 +22,10 @@ class Bridge { return this.electronWrapper_.window(); } + newBrowserWindow(options) { + return new BrowserWindow(options); + } + windowContentSize() { if (!this.window()) return { width: 0, height: 0 }; const s = this.window().getContentSize(); diff --git a/ElectronClient/app/gui/NoteText.jsx b/ElectronClient/app/gui/NoteText.jsx index 7d4843479..3cf078cbc 100644 --- a/ElectronClient/app/gui/NoteText.jsx +++ b/ElectronClient/app/gui/NoteText.jsx @@ -7,6 +7,7 @@ const Folder = require('lib/models/Folder.js'); const Tag = require('lib/models/Tag.js'); const { time } = require('lib/time-utils.js'); const Setting = require('lib/models/Setting.js'); +const InteropServiceHelper = require('../InteropServiceHelper.js'); const { IconButton } = require('./IconButton.min.js'); const { urlDecode, substrWithEllipsis } = require('lib/string-utils'); const Toolbar = require('./Toolbar.min.js'); @@ -942,6 +943,8 @@ class NoteTextComponent extends React.Component { } webview_domReady() { + + console.info('webview_domReady', this.webviewRef_.current); if (!this.webviewRef_.current) return; this.setState({ @@ -1224,11 +1227,6 @@ class NoteTextComponent extends React.Component { }); } - // helper function to style the title for printing - title_(title) { - return `
${title}

`; - } - async printTo_(target, options) { if (this.props.selectedNoteIds.length !== 1 || !this.webviewRef_.current) { throw new Error(_('Only one note can be printed or exported to PDF at a time.')); @@ -1240,36 +1238,50 @@ class NoteTextComponent extends React.Component { } this.isPrinting_ = true; - const previousBody = this.state.note.body; - const tempBody = `${this.title_(this.state.note.title)}\n\n${previousBody}`; - const previousTheme = Setting.value('theme'); - Setting.setValue('theme', Setting.THEME_LIGHT); - this.lastSetHtml_ = ''; - await this.updateHtml(this.state.note.markup_language, tempBody, { useCustomCss: true }); - this.forceUpdate(); + // const previousBody = this.state.note.body; + // const tempBody = `${this.state.note.title}\n\n${previousBody}`; - const restoreSettings = async () => { - Setting.setValue('theme', previousTheme); - this.lastSetHtml_ = ''; - await this.updateHtml(this.state.note.markup_language, previousBody); - this.forceUpdate(); - }; + // const previousTheme = Setting.value('theme'); + // Setting.setValue('theme', Setting.THEME_LIGHT); + // this.lastSetHtml_ = ''; + // await this.updateHtml(this.state.note.markup_language, tempBody, { useCustomCss: true }); + // this.forceUpdate(); - setTimeout(() => { + // const restoreSettings = async () => { + // Setting.setValue('theme', previousTheme); + // this.lastSetHtml_ = ''; + // await this.updateHtml(this.state.note.markup_language, previousBody); + // this.forceUpdate(); + // }; + + // Need to save because the interop service reloads the note from the database + await this.saveIfNeeded(); + + setTimeout(async () => { if (target === 'pdf') { - this.webviewRef_.current.wrappedInstance.printToPDF({ printBackground: true, pageSize: Setting.value('export.pdfPageSize'), landscape: Setting.value('export.pdfPageOrientation') === 'landscape' }, (error, data) => { - restoreSettings(); - - if (error) { - bridge().showErrorMessageBox(error.message); - } else { - shim.fsDriver().writeFile(options.path, data, 'buffer'); - } - }); + try { + const pdfData = await InteropServiceHelper.exportNoteToPdf(this.state.note.id, { + printBackground: true, + pageSize: Setting.value('export.pdfPageSize'), + landscape: Setting.value('export.pdfPageOrientation') === 'landscape', + }); + shim.fsDriver().writeFile(options.path, pdfData, 'buffer'); + } catch (error) { + console.error(error); + bridge().showErrorMessageBox(error.message); + } } else if (target === 'printer') { - this.webviewRef_.current.wrappedInstance.print({ printBackground: true }); - restoreSettings(); + try { + await InteropServiceHelper.printNote(this.state.note.id, { + printBackground: true, + }); + } catch (error) { + console.error(error); + bridge().showErrorMessageBox(error.message); + } + + // restoreSettings(); } this.isPrinting_ = false; }, 100); diff --git a/ElectronClient/app/gui/NoteTextViewer.jsx b/ElectronClient/app/gui/NoteTextViewer.jsx index a8325a8f7..ce37a9fcb 100644 --- a/ElectronClient/app/gui/NoteTextViewer.jsx +++ b/ElectronClient/app/gui/NoteTextViewer.jsx @@ -5,10 +5,6 @@ class NoteTextViewerComponent extends React.Component { constructor() { super(); - this.state = { - 'html': '', - }; - this.initialized_ = false; this.domReady_ = false; diff --git a/ReactNativeClient/lib/services/InteropService_Exporter_Html.js b/ReactNativeClient/lib/services/InteropService_Exporter_Html.js index 1b61ba6ff..6fdb78ca4 100644 --- a/ReactNativeClient/lib/services/InteropService_Exporter_Html.js +++ b/ReactNativeClient/lib/services/InteropService_Exporter_Html.js @@ -10,6 +10,7 @@ const MarkupToHtml = require('lib/renderers/MarkupToHtml.js'); const dataurl = require('dataurl'); const { themeStyle } = require('../../theme.js'); const { dirname } = require('lib/path-utils.js'); +const { escapeHtml } = require('lib/string-utils.js'); class InteropService_Exporter_Html extends InteropService_Exporter_Base { @@ -104,10 +105,23 @@ class InteropService_Exporter_Html extends InteropService_Exporter_Base { const bodyMd = await this.processNoteResources_(item); const result = this.markupToHtml_.render(item.markup_language, bodyMd, this.style_, { resources: this.resources_, plainResourceRendering: true }); const noteContent = []; - if (item.title) noteContent.push(`
${item.title}
`); + if (item.title) noteContent.push(`
${escapeHtml(item.title)}
`); if (result.html) noteContent.push(result.html); - await shim.fsDriver().writeFile(noteFilePath, `
${noteContent.join('\n\n')}
`, 'utf-8'); + const fullHtml = ` + + + + + ${escapeHtml(item.title)} + + +
${noteContent.join('\n\n')}
+ + + `; + + await shim.fsDriver().writeFile(noteFilePath, fullHtml, 'utf-8'); } }