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 + '' + tag + '>';
+ 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