diff --git a/packages/app-cli/tests/html_to_md/linebreaks.md b/packages/app-cli/tests/html_to_md/linebreaks.md
index 116e9d1cb..21edaaadb 100644
--- a/packages/app-cli/tests/html_to_md/linebreaks.md
+++ b/packages/app-cli/tests/html_to_md/linebreaks.md
@@ -15,7 +15,4 @@ however.
Because it isn't
necessary.
-
-
-
-...
\ No newline at end of file
+
...
\ No newline at end of file
diff --git a/packages/app-cli/tests/html_to_md/repeated_brs.html b/packages/app-cli/tests/html_to_md/repeated_brs.html
new file mode 100644
index 000000000..9d8e90668
--- /dev/null
+++ b/packages/app-cli/tests/html_to_md/repeated_brs.html
@@ -0,0 +1,4 @@
+A
test.
+
+A single <br/>
can use two spaces at the end of the line,
+but
the markdown renderer discards these if the line is otherwise empty.
\ No newline at end of file
diff --git a/packages/app-cli/tests/html_to_md/repeated_brs.md b/packages/app-cli/tests/html_to_md/repeated_brs.md
new file mode 100644
index 000000000..6d425697f
--- /dev/null
+++ b/packages/app-cli/tests/html_to_md/repeated_brs.md
@@ -0,0 +1,5 @@
+A
+
test.
+A single <br/>
+can use two spaces at the end of the line, but
+
the markdown renderer discards these if the line is otherwise empty.
\ No newline at end of file
diff --git a/packages/turndown/src/commonmark-rules.js b/packages/turndown/src/commonmark-rules.js
index 190948d81..8ff223d5d 100644
--- a/packages/turndown/src/commonmark-rules.js
+++ b/packages/turndown/src/commonmark-rules.js
@@ -45,11 +45,18 @@ rules.paragraph = {
rules.lineBreak = {
filter: 'br',
- replacement: function (content, node, options) {
+ replacement: function (_content, node, options, previousNode) {
+ let brReplacement = options.br + '\n';
+
// Code blocks may include
s -- replacing them should not be necessary
// in code blocks.
- const brReplacement = node.isCode ? '' : options.br;
- return brReplacement + '\n'
+ if (node.isCode) {
+ brReplacement = '\n';
+ } else if (previousNode && previousNode.nodeName === 'BR') {
+ brReplacement = '
';
+ }
+
+ return brReplacement;
}
}
diff --git a/packages/turndown/src/turndown.js b/packages/turndown/src/turndown.js
index 1d38839d3..71fb54dfa 100644
--- a/packages/turndown/src/turndown.js
+++ b/packages/turndown/src/turndown.js
@@ -165,27 +165,32 @@ TurndownService.prototype = {
function process (parentNode, escapeContent = 'auto') {
if (this.options.disableEscapeContent) escapeContent = false;
- var self = this
- return reduce.call(parentNode.childNodes, function (output, node) {
- node = new Node(node, self.options)
+ let output = '';
+ let previousNode = null;
+
+ for (let node of parentNode.childNodes) {
+ node = new Node(node, this.options);
var replacement = ''
if (node.nodeType === 3) {
if (node.isCode || escapeContent === false) {
replacement = node.nodeValue
} else {
- replacement = self.escape(node.nodeValue)
+ replacement = this.escape(node.nodeValue);
// Escape < and > so that, for example, this kind of HTML text: "This is a tag: <p>" is still rendered as "This is a tag: <p>"
// and not "This is a tag:
". If the latter, it means the HTML will be rendered if the viewer supports HTML (which, in Joplin, it does). replacement = replacement.replace(/<(.+?)>/g, '<$1>'); } } else if (node.nodeType === 1) { - replacement = replacementForNode.call(self, node) + replacement = replacementForNode.call(this, node, previousNode); } - return join(output, replacement) - }, '') + output = join(output, replacement); + previousNode = node; + } + + return output; } /** @@ -211,11 +216,12 @@ function postProcess (output) { * Converts an element node to its Markdown equivalent * @private * @param {HTMLElement} node The node to convert + * @param {HTMLElement|null} previousNode The node immediately before this node. * @returns A Markdown representation of the node * @type String */ -function replacementForNode (node) { +function replacementForNode (node, previousNode) { var rule = this.rules.forNode(node) var content = process.call(this, node, rule.escapeContent ? rule.escapeContent(node) : 'auto') var whitespace = node.flankingWhitespace @@ -223,7 +229,7 @@ function replacementForNode (node) { return ( whitespace.leading + - rule.replacement(content, node, this.options) + + rule.replacement(content, node, this.options, previousNode) + whitespace.trailing ) }