From 832e9454c77a285828b7ebb36784716696231879 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Sun, 27 Aug 2023 19:01:06 +0100 Subject: [PATCH] Desktop: Resolves #8579: Allow more special content within tables in the Rich Text editor --- .eslintignore | 1 - .gitignore | 1 - packages/app-cli/tests/HtmlToMd.ts | 8 ++-- packages/app-cli/tests/html_to_md/code_1.html | 8 ---- packages/app-cli/tests/html_to_md/code_1.md | 7 --- .../html_to_md/table_with_blockquote.html | 14 ++++++ .../tests/html_to_md/table_with_blockquote.md | 1 + .../tests/html_to_md/table_with_code_1.html | 15 +++++++ .../tests/html_to_md/table_with_code_1.md | 1 + .../tests/html_to_md/table_with_code_2.html | 22 ++++++++++ .../tests/html_to_md/table_with_code_2.md | 1 + .../tests/html_to_md/table_with_heading.html | 14 ++++++ .../tests/html_to_md/table_with_heading.md | 1 + .../tests/html_to_md/table_with_hr.html | 14 ++++++ .../app-cli/tests/html_to_md/table_with_hr.md | 1 + .../tests/html_to_md/table_with_list.html | 1 + .../tests/html_to_md/table_with_list.md | 1 + packages/turndown-plugin-gfm/src/tables.js | 44 ++++++++++++++++--- packages/turndown/src/commonmark-rules.js | 4 +- packages/turndown/src/turndown.js | 7 ++- 20 files changed, 139 insertions(+), 27 deletions(-) delete mode 100644 packages/app-cli/tests/html_to_md/code_1.html delete mode 100644 packages/app-cli/tests/html_to_md/code_1.md create mode 100644 packages/app-cli/tests/html_to_md/table_with_blockquote.html create mode 100644 packages/app-cli/tests/html_to_md/table_with_blockquote.md create mode 100644 packages/app-cli/tests/html_to_md/table_with_code_1.html create mode 100644 packages/app-cli/tests/html_to_md/table_with_code_1.md create mode 100644 packages/app-cli/tests/html_to_md/table_with_code_2.html create mode 100644 packages/app-cli/tests/html_to_md/table_with_code_2.md create mode 100644 packages/app-cli/tests/html_to_md/table_with_heading.html create mode 100644 packages/app-cli/tests/html_to_md/table_with_heading.md create mode 100644 packages/app-cli/tests/html_to_md/table_with_hr.html create mode 100644 packages/app-cli/tests/html_to_md/table_with_hr.md create mode 100644 packages/app-cli/tests/html_to_md/table_with_list.html create mode 100644 packages/app-cli/tests/html_to_md/table_with_list.md diff --git a/.eslintignore b/.eslintignore index 36900fb49..58b8b5eb7 100644 --- a/.eslintignore +++ b/.eslintignore @@ -552,7 +552,6 @@ packages/lib/database-driver-better-sqlite.js packages/lib/database.js packages/lib/debug/DebugService.js packages/lib/dom.js -packages/lib/dummy.test.js packages/lib/errorUtils.js packages/lib/errors.js packages/lib/eventManager.js diff --git a/.gitignore b/.gitignore index 544dc3f20..6c49c5ee2 100644 --- a/.gitignore +++ b/.gitignore @@ -538,7 +538,6 @@ packages/lib/database-driver-better-sqlite.js packages/lib/database.js packages/lib/debug/DebugService.js packages/lib/dom.js -packages/lib/dummy.test.js packages/lib/errorUtils.js packages/lib/errors.js packages/lib/eventManager.js diff --git a/packages/app-cli/tests/HtmlToMd.ts b/packages/app-cli/tests/HtmlToMd.ts index 99da3a7e4..3333cd676 100644 --- a/packages/app-cli/tests/HtmlToMd.ts +++ b/packages/app-cli/tests/HtmlToMd.ts @@ -1,5 +1,6 @@ import shim from '@joplin/lib/shim'; const os = require('os'); +import { readFile } from 'fs/promises'; const { filename } = require('@joplin/lib/path-utils'); import HtmlToMd from '@joplin/lib/HtmlToMd'; @@ -35,8 +36,8 @@ describe('HtmlToMd', () => { htmlToMdOptions.preserveImageTagsWithSize = true; } - const html = await shim.fsDriver().readFile(htmlPath); - let expectedMd = await shim.fsDriver().readFile(mdPath); + const html = await readFile(htmlPath, 'utf8'); + let expectedMd = await readFile(mdPath, 'utf8'); let actualMd = await htmlToMd.parse(`
${html}
`, htmlToMdOptions); @@ -47,11 +48,12 @@ describe('HtmlToMd', () => { if (actualMd !== expectedMd) { const result = []; - result.push(''); result.push(`Error converting file: ${htmlFilename}`); result.push('--------------------------------- Got:'); result.push(actualMd.split('\n').map((l: string) => `"${l}"`).join('\n')); + // result.push('--------------------------------- Raw:'); + // result.push(actualMd.split('\n')); result.push('--------------------------------- Expected:'); result.push(expectedMd.split('\n').map((l: string) => `"${l}"`).join('\n')); result.push('--------------------------------------------'); diff --git a/packages/app-cli/tests/html_to_md/code_1.html b/packages/app-cli/tests/html_to_md/code_1.html deleted file mode 100644 index ad80a4535..000000000 --- a/packages/app-cli/tests/html_to_md/code_1.html +++ /dev/null @@ -1,8 +0,0 @@ -
-
def ma_fonction():
-    """
-        C'est une super fonction
-    """
-    pass
- -
\ No newline at end of file diff --git a/packages/app-cli/tests/html_to_md/code_1.md b/packages/app-cli/tests/html_to_md/code_1.md deleted file mode 100644 index 9a9bf67bd..000000000 --- a/packages/app-cli/tests/html_to_md/code_1.md +++ /dev/null @@ -1,7 +0,0 @@ -``` -def ma_fonction(): - """ - C'est une super fonction - """ - pass -``` \ No newline at end of file diff --git a/packages/app-cli/tests/html_to_md/table_with_blockquote.html b/packages/app-cli/tests/html_to_md/table_with_blockquote.html new file mode 100644 index 000000000..75565568e --- /dev/null +++ b/packages/app-cli/tests/html_to_md/table_with_blockquote.html @@ -0,0 +1,14 @@ + + + + + + + + + + + + + +
AB

Finally, from so little sleeping and so much reading, his brain dried up and he went completely out of his mind.

- Miguel de Cervantes

d
\ No newline at end of file diff --git a/packages/app-cli/tests/html_to_md/table_with_blockquote.md b/packages/app-cli/tests/html_to_md/table_with_blockquote.md new file mode 100644 index 000000000..87cfdc5f2 --- /dev/null +++ b/packages/app-cli/tests/html_to_md/table_with_blockquote.md @@ -0,0 +1 @@ +
AB

Finally, from so little sleeping and so much reading, his brain dried up and he went completely out of his mind.

- Miguel de Cervantes

d
\ No newline at end of file diff --git a/packages/app-cli/tests/html_to_md/table_with_code_1.html b/packages/app-cli/tests/html_to_md/table_with_code_1.html new file mode 100644 index 000000000..50db0c626 --- /dev/null +++ b/packages/app-cli/tests/html_to_md/table_with_code_1.html @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + +
CodeDescription
const test = "hello";
abcd
const test = "hello";
abcd
\ No newline at end of file diff --git a/packages/app-cli/tests/html_to_md/table_with_code_1.md b/packages/app-cli/tests/html_to_md/table_with_code_1.md new file mode 100644 index 000000000..6f0bfbaa0 --- /dev/null +++ b/packages/app-cli/tests/html_to_md/table_with_code_1.md @@ -0,0 +1 @@ +
CodeDescription
const test = "hello";
abcd
const test = "hello";
abcd
\ No newline at end of file diff --git a/packages/app-cli/tests/html_to_md/table_with_code_2.html b/packages/app-cli/tests/html_to_md/table_with_code_2.html new file mode 100644 index 000000000..54d90f2bf --- /dev/null +++ b/packages/app-cli/tests/html_to_md/table_with_code_2.html @@ -0,0 +1,22 @@ +
+ + + + + + + + + + + + + + + +
CodeDescription
+
const test = "hello";
+
abcda
+
const test = "hello";
+
abcd
\ No newline at end of file diff --git a/packages/app-cli/tests/html_to_md/table_with_code_2.md b/packages/app-cli/tests/html_to_md/table_with_code_2.md new file mode 100644 index 000000000..0bf437036 --- /dev/null +++ b/packages/app-cli/tests/html_to_md/table_with_code_2.md @@ -0,0 +1 @@ +
CodeDescription
const test = "hello";
abcda
const test = "hello";
abcd
\ No newline at end of file diff --git a/packages/app-cli/tests/html_to_md/table_with_heading.html b/packages/app-cli/tests/html_to_md/table_with_heading.html new file mode 100644 index 000000000..1620fffd8 --- /dev/null +++ b/packages/app-cli/tests/html_to_md/table_with_heading.html @@ -0,0 +1,14 @@ + + + + + + + + + + + + + +
AB

Testing

hello

d
\ No newline at end of file diff --git a/packages/app-cli/tests/html_to_md/table_with_heading.md b/packages/app-cli/tests/html_to_md/table_with_heading.md new file mode 100644 index 000000000..9db794178 --- /dev/null +++ b/packages/app-cli/tests/html_to_md/table_with_heading.md @@ -0,0 +1 @@ +
AB

Testing

hello

d
\ No newline at end of file diff --git a/packages/app-cli/tests/html_to_md/table_with_hr.html b/packages/app-cli/tests/html_to_md/table_with_hr.html new file mode 100644 index 000000000..359d90252 --- /dev/null +++ b/packages/app-cli/tests/html_to_md/table_with_hr.html @@ -0,0 +1,14 @@ + + + + + + + + + + + + + +
AB
One line
Two line
d
\ No newline at end of file diff --git a/packages/app-cli/tests/html_to_md/table_with_hr.md b/packages/app-cli/tests/html_to_md/table_with_hr.md new file mode 100644 index 000000000..ae56fd6f2 --- /dev/null +++ b/packages/app-cli/tests/html_to_md/table_with_hr.md @@ -0,0 +1 @@ +
AB
One line
Two line
d
\ No newline at end of file diff --git a/packages/app-cli/tests/html_to_md/table_with_list.html b/packages/app-cli/tests/html_to_md/table_with_list.html new file mode 100644 index 000000000..933cf5fb8 --- /dev/null +++ b/packages/app-cli/tests/html_to_md/table_with_list.html @@ -0,0 +1 @@ +
Header 1Header 2

  • Check 1
  • Check 2
\ No newline at end of file diff --git a/packages/app-cli/tests/html_to_md/table_with_list.md b/packages/app-cli/tests/html_to_md/table_with_list.md new file mode 100644 index 000000000..933cf5fb8 --- /dev/null +++ b/packages/app-cli/tests/html_to_md/table_with_list.md @@ -0,0 +1 @@ +
Header 1Header 2

  • Check 1
  • Check 2
\ No newline at end of file diff --git a/packages/turndown-plugin-gfm/src/tables.js b/packages/turndown-plugin-gfm/src/tables.js index 1978e5b03..10909ec8a 100644 --- a/packages/turndown-plugin-gfm/src/tables.js +++ b/packages/turndown-plugin-gfm/src/tables.js @@ -3,6 +3,8 @@ var every = Array.prototype.every var rules = {} var alignMap = { left: ':---', right: '---:', center: ':---:' }; +let isCodeBlock_ = null; + function getAlignment(node) { return node ? (node.getAttribute('align') || node.style.textAlign || '').toLowerCase() : ''; } @@ -65,10 +67,10 @@ rules.tableRow = { } rules.table = { - // Only convert tables with a heading row. - // Tables with no heading row are kept using `keep` (see below). + // Only convert tables that can result in valid Markdown + // Other tables are kept as HTML using `keep` (see below). filter: function (node) { - return node.nodeName === 'TABLE' + return node.nodeName === 'TABLE' && !tableShouldBeHtml(node); }, replacement: function (content, node) { @@ -154,6 +156,35 @@ function nodeContainsTable(node) { return false; } +const nodeContains = (node, types) => { + if (!node.childNodes) return false; + + for (let i = 0; i < node.childNodes.length; i++) { + const child = node.childNodes[i]; + if (types === 'code' && isCodeBlock_(child)) return true; + if (types.includes(child.nodeName)) return true; + if (nodeContains(child, types)) return true; + } + + return false; +} + +const tableShouldBeHtml = (tableNode) => { + return nodeContains(tableNode, 'code') || + nodeContains(tableNode, [ + 'UL', + 'OL', + 'H1', + 'H2', + 'H3', + 'H4', + 'H5', + 'H6', + 'HR', + 'BLOCKQUOTE', + ]); +} + // Various conditions under which a table should be skipped - i.e. each cell // will be rendered one after the other as if they were paragraphs. function tableShouldBeSkipped(tableNode) { @@ -192,8 +223,11 @@ function tableColCount(node) { } export default function tables (turndownService) { + isCodeBlock_ = turndownService.isCodeBlock; + turndownService.keep(function (node) { - return node.nodeName === 'TABLE' - }) + if (node.nodeName === 'TABLE' && tableShouldBeHtml(node)) return true; + return false; + }); for (var key in rules) turndownService.addRule(key, rules[key]) } diff --git a/packages/turndown/src/commonmark-rules.js b/packages/turndown/src/commonmark-rules.js index 66a39896b..7f4fcdeda 100644 --- a/packages/turndown/src/commonmark-rules.js +++ b/packages/turndown/src/commonmark-rules.js @@ -617,7 +617,9 @@ rules.mathjaxScriptBlock = { rules.joplinHtmlInMarkdown = { filter: function (node) { - return node && node.classList && node.classList.contains('jop-noMdConv'); + // Tables are special because they may be entirely kept as HTML depending on + // the logic in table.js, for example if they contain code. + return node && node.classList && node.classList.contains('jop-noMdConv') && node.nodeName !== 'TABLE'; }, replacement: function (content, node) { diff --git a/packages/turndown/src/turndown.js b/packages/turndown/src/turndown.js index 52ec44a1f..973dbdcba 100644 --- a/packages/turndown/src/turndown.js +++ b/packages/turndown/src/turndown.js @@ -145,7 +145,12 @@ TurndownService.prototype = { return escapes.reduce(function (accumulator, escape) { return accumulator.replace(escape[0], escape[1]) }, string) - } + }, + + isCodeBlock: function(node) { + return isCodeBlock(node); + }, + } /**