diff --git a/CliClient/tests/EnexToMd.js b/CliClient/tests/EnexToMd.js index 3c09a1529..0b40857b0 100644 --- a/CliClient/tests/EnexToMd.js +++ b/CliClient/tests/EnexToMd.js @@ -1,5 +1,6 @@ require('app-module-path').addPath(__dirname); +const os = require('os'); const { time } = require('lib/time-utils.js'); const { filename } = require('lib/path-utils.js'); const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js'); @@ -37,9 +38,14 @@ describe('EnexToMd', function() { // if (htmlFilename !== 'text2.html') continue; const html = await shim.fsDriver().readFile(htmlPath); - const expectedMd = await shim.fsDriver().readFile(mdPath); + let expectedMd = await shim.fsDriver().readFile(mdPath); - const actualMd = await enexXmlToMd('
' + html + '
', []); + let actualMd = await enexXmlToMd('
' + html + '
', []); + + if (os.EOL === '\r\n') { + expectedMd = expectedMd.replace(/\r\n/g, '\n') + actualMd = actualMd.replace(/\r\n/g, '\n') + } if (actualMd !== expectedMd) { console.info(''); diff --git a/CliClient/tests/HtmlToMd.js b/CliClient/tests/HtmlToMd.js index fe935d666..e163a2e96 100644 --- a/CliClient/tests/HtmlToMd.js +++ b/CliClient/tests/HtmlToMd.js @@ -1,5 +1,6 @@ require('app-module-path').addPath(__dirname); +const os = require('os'); const { time } = require('lib/time-utils.js'); const { filename } = require('lib/path-utils.js'); const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js'); @@ -39,9 +40,14 @@ describe('HtmlToMd', function() { // if (htmlFilename !== 'anchor_with_url_with_spaces.html') continue; const html = await shim.fsDriver().readFile(htmlPath); - const expectedMd = await shim.fsDriver().readFile(mdPath); + let expectedMd = await shim.fsDriver().readFile(mdPath); - const actualMd = await htmlToMd.parse('
' + html + '
', []); + let actualMd = await htmlToMd.parse('
' + html + '
', []); + + if (os.EOL === '\r\n') { + expectedMd = expectedMd.replace(/\r\n/g, '\n') + actualMd = actualMd.replace(/\r\n/g, '\n') + } if (actualMd !== expectedMd) { console.info(''); diff --git a/ElectronClient/app/gui/NoteText.jsx b/ElectronClient/app/gui/NoteText.jsx index fc839d51d..932971d6c 100644 --- a/ElectronClient/app/gui/NoteText.jsx +++ b/ElectronClient/app/gui/NoteText.jsx @@ -6,7 +6,7 @@ const Search = require('lib/models/Search.js'); const { time } = require('lib/time-utils.js'); const Setting = require('lib/models/Setting.js'); const { IconButton } = require('./IconButton.min.js'); -const { urlDecode } = require('lib/string-utils'); +const { urlDecode, escapeHtml } = require('lib/string-utils'); const Toolbar = require('./Toolbar.min.js'); const TagList = require('./TagList.min.js'); const { connect } = require('react-redux'); @@ -864,20 +864,7 @@ class NoteTextComponent extends React.Component { let commandProcessed = true; if (command.name === 'exportPdf' && this.webview_) { - const path = bridge().showSaveDialog({ - filters: [{ name: _('PDF File'), extensions: ['pdf']}], - defaultPath: safeFilename(this.state.note.title), - }); - - if (path) { - this.webview_.printToPDF({}, (error, data) => { - if (error) { - bridge().showErrorMessageBox(error.message); - } else { - shim.fsDriver().writeFile(path, data, 'buffer'); - } - }); - } + this.commandSavePdf(); } else if (command.name === 'print' && this.webview_) { this.webview_.print(); } else if (command.name === 'textBold') { @@ -942,6 +929,44 @@ class NoteTextComponent extends React.Component { }); } + commandSavePdf() { + const path = bridge().showSaveDialog({ + filters: [{ name: _('PDF File'), extensions: ['pdf']}], + defaultPath: safeFilename(this.state.note.title), + }); + + if (path) { + // Temporarily add a

title in the webview + const newHtml = this.insertHtmlHeading_(this.lastSetHtml_, this.state.note.title); + this.webview_.send('setHtml', newHtml); + + setTimeout(() => { + this.webview_.printToPDF({}, (error, data) => { + if (error) { + bridge().showErrorMessageBox(error.message); + } else { + shim.fsDriver().writeFile(path, data, 'buffer'); + } + + // Refresh the webview, restoring the previous content + this.lastSetHtml_ = ''; + this.forceUpdate(); + }); + }, 100); + } + } + + insertHtmlHeading_(s, heading) { + const tag = 'h2'; + const marker = '' + let splitStyle = s.split(marker); + const index = splitStyle.length > 1 ? 1 : 0; + let toInsert = escapeHtml(heading); + toInsert = '<' + tag + '>' + toInsert + ''; + splitStyle[index] = toInsert + splitStyle[index]; + return splitStyle.join(marker); + } + externalEditWatcher() { if (!this.externalEditWatcher_) { this.externalEditWatcher_ = new ExternalEditWatcher((action) => { return this.props.dispatch(action) }); diff --git a/ReactNativeClient/lib/MdToHtml.js b/ReactNativeClient/lib/MdToHtml.js index 842a832bf..ffd299cd0 100644 --- a/ReactNativeClient/lib/MdToHtml.js +++ b/ReactNativeClient/lib/MdToHtml.js @@ -393,6 +393,8 @@ class MdToHtml { previousToken = t; } + output.unshift(''); + // Insert the extra CSS at the top of the HTML if (!ObjectUtils.isEmpty(extraCssBlocks)) { diff --git a/ReactNativeClient/lib/string-utils.js b/ReactNativeClient/lib/string-utils.js index 780ac000c..36b330809 100644 --- a/ReactNativeClient/lib/string-utils.js +++ b/ReactNativeClient/lib/string-utils.js @@ -210,4 +210,13 @@ function urlDecode(string) { return decodeURIComponent((string+'').replace(/\+/g, '%20')); } -module.exports = { removeDiacritics, escapeFilename, wrap, splitCommandString, padLeft, toTitleCase }; \ No newline at end of file +function escapeHtml(s) { + return s + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +module.exports = { removeDiacritics, escapeFilename, wrap, splitCommandString, padLeft, toTitleCase, escapeHtml }; \ No newline at end of file