1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Desktop: Resolves #8579: Allow more special content within tables in the Rich Text editor

This commit is contained in:
Laurent Cozic 2023-08-27 19:01:06 +01:00
parent e4cb871c11
commit 832e9454c7
20 changed files with 139 additions and 27 deletions

View File

@ -552,7 +552,6 @@ packages/lib/database-driver-better-sqlite.js
packages/lib/database.js packages/lib/database.js
packages/lib/debug/DebugService.js packages/lib/debug/DebugService.js
packages/lib/dom.js packages/lib/dom.js
packages/lib/dummy.test.js
packages/lib/errorUtils.js packages/lib/errorUtils.js
packages/lib/errors.js packages/lib/errors.js
packages/lib/eventManager.js packages/lib/eventManager.js

1
.gitignore vendored
View File

@ -538,7 +538,6 @@ packages/lib/database-driver-better-sqlite.js
packages/lib/database.js packages/lib/database.js
packages/lib/debug/DebugService.js packages/lib/debug/DebugService.js
packages/lib/dom.js packages/lib/dom.js
packages/lib/dummy.test.js
packages/lib/errorUtils.js packages/lib/errorUtils.js
packages/lib/errors.js packages/lib/errors.js
packages/lib/eventManager.js packages/lib/eventManager.js

View File

@ -1,5 +1,6 @@
import shim from '@joplin/lib/shim'; import shim from '@joplin/lib/shim';
const os = require('os'); const os = require('os');
import { readFile } from 'fs/promises';
const { filename } = require('@joplin/lib/path-utils'); const { filename } = require('@joplin/lib/path-utils');
import HtmlToMd from '@joplin/lib/HtmlToMd'; import HtmlToMd from '@joplin/lib/HtmlToMd';
@ -35,8 +36,8 @@ describe('HtmlToMd', () => {
htmlToMdOptions.preserveImageTagsWithSize = true; htmlToMdOptions.preserveImageTagsWithSize = true;
} }
const html = await shim.fsDriver().readFile(htmlPath); const html = await readFile(htmlPath, 'utf8');
let expectedMd = await shim.fsDriver().readFile(mdPath); let expectedMd = await readFile(mdPath, 'utf8');
let actualMd = await htmlToMd.parse(`<div>${html}</div>`, htmlToMdOptions); let actualMd = await htmlToMd.parse(`<div>${html}</div>`, htmlToMdOptions);
@ -47,11 +48,12 @@ describe('HtmlToMd', () => {
if (actualMd !== expectedMd) { if (actualMd !== expectedMd) {
const result = []; const result = [];
result.push(''); result.push('');
result.push(`Error converting file: ${htmlFilename}`); result.push(`Error converting file: ${htmlFilename}`);
result.push('--------------------------------- Got:'); result.push('--------------------------------- Got:');
result.push(actualMd.split('\n').map((l: string) => `"${l}"`).join('\n')); result.push(actualMd.split('\n').map((l: string) => `"${l}"`).join('\n'));
// result.push('--------------------------------- Raw:');
// result.push(actualMd.split('\n'));
result.push('--------------------------------- Expected:'); result.push('--------------------------------- Expected:');
result.push(expectedMd.split('\n').map((l: string) => `"${l}"`).join('\n')); result.push(expectedMd.split('\n').map((l: string) => `"${l}"`).join('\n'));
result.push('--------------------------------------------'); result.push('--------------------------------------------');

View File

@ -1,8 +0,0 @@
<div>
<table><tbody><tr><td class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">def</span> ma_fonction<span style="color: black;">(</span><span style="color: black;">)</span>:
<span style="color: #483d8b;">"""
C'est une super fonction
"""</span>
<span style="color: #ff7700;font-weight:bold;">pass</span></pre></td></tr></tbody></table>
</div>

View File

@ -1,7 +0,0 @@
```
def ma_fonction():
"""
C'est une super fonction
"""
pass
```

View File

@ -0,0 +1,14 @@
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
</tr>
</thead>
<tbody>
<tr>
<td><blockquote><p>Finally, from so little sleeping and so much reading, his brain dried up and he went completely out of his mind.</p><p>- Miguel de Cervantes</p></blockquote></td>
<td>d</td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1 @@
<table><thead><tr><th>A</th><th>B</th></tr></thead><tbody><tr><td><blockquote><p>Finally, from so little sleeping and so much reading, his brain dried up and he went completely out of his mind.</p><p>- Miguel de Cervantes</p></blockquote></td><td>d</td></tr></tbody></table>

View File

@ -0,0 +1,15 @@
<table>
<thead>
<tr>
<th>Code</th><th>Description</th>
</tr>
<tr>
<td><pre><code>const test = "hello";</code></pre></td>
<td>abcd</td>
</tr>
<tr>
<td><pre><code>const test = "hello";</code></pre></td>
<td>abcd</td>
</tr>
</thead>
</table>

View File

@ -0,0 +1 @@
<table><thead><tr><th>Code</th><th>Description</th></tr><tr><td><pre><code>const test = "hello";</code></pre></td><td>abcd</td></tr><tr><td><pre><code>const test = "hello";</code></pre></td><td>abcd</td></tr></thead></table>

View File

@ -0,0 +1,22 @@
<div id="rendered-md">
<table class="jop-noMdConv">
<thead class="jop-noMdConv">
<tr class="jop-noMdConv">
<th class="jop-noMdConv">Code</th>
<th class="jop-noMdConv">Description</th>
</tr>
<tr class="jop-noMdConv">
<td class="jop-noMdConv">
<pre class="jop-noMdConv"><code class="jop-noMdConv">const test = "hello";</code></pre>
</td>
<td class="jop-noMdConv">abcda</td>
</tr>
<tr class="jop-noMdConv">
<td class="jop-noMdConv">
<pre class="jop-noMdConv"><code class="jop-noMdConv">const test = "hello";</code></pre>
</td>
<td class="jop-noMdConv">abcd</td>
</tr>
</thead>
</table
</div>

View File

@ -0,0 +1 @@
<table class="jop-noMdConv"><thead class="jop-noMdConv"><tr class="jop-noMdConv"><th class="jop-noMdConv">Code</th><th class="jop-noMdConv">Description</th></tr><tr class="jop-noMdConv"><td class="jop-noMdConv"><pre class="jop-noMdConv"><code class="">const test = "hello";</code></pre></td><td class="jop-noMdConv">abcda</td></tr><tr class="jop-noMdConv"><td class="jop-noMdConv"><pre class="jop-noMdConv"><code class="">const test = "hello";</code></pre></td><td class="jop-noMdConv">abcd</td></tr></thead></table>

View File

@ -0,0 +1,14 @@
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
</tr>
</thead>
<tbody>
<tr>
<td><h1>Testing</h1><p>hello</p></td>
<td>d</td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1 @@
<table><thead><tr><th>A</th><th>B</th></tr></thead><tbody><tr><td><h1>Testing</h1><p>hello</p></td><td>d</td></tr></tbody></table>

View File

@ -0,0 +1,14 @@
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
</tr>
</thead>
<tbody>
<tr>
<td>One line<hr/>Two line</td>
<td>d</td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1 @@
<table><thead><tr><th>A</th><th>B</th></tr></thead><tbody><tr><td>One line<hr>Two line</td><td>d</td></tr></tbody></table>

View File

@ -0,0 +1 @@
<table border="1" style="border-collapse: collapse; width: 100%;" data-mce-selected="1"><tbody><tr><td style="width: 50.0518%;">Header 1</td><td style="width: 50.0518%;">Header 2</td></tr><tr><td style="width: 50.0518%;"><br></td><td style="width: 50.0518%;"><ul><li>Check 1</li><li>Check 2</li></ul></td></tr></tbody></table>

View File

@ -0,0 +1 @@
<table border="1" style="border-collapse: collapse; width: 100%;" data-mce-selected="1"><tbody><tr><td style="width: 50.0518%;">Header 1</td><td style="width: 50.0518%;">Header 2</td></tr><tr><td style="width: 50.0518%;"><br></td><td style="width: 50.0518%;"><ul><li>Check 1</li><li>Check 2</li></ul></td></tr></tbody></table>

View File

@ -3,6 +3,8 @@ var every = Array.prototype.every
var rules = {} var rules = {}
var alignMap = { left: ':---', right: '---:', center: ':---:' }; var alignMap = { left: ':---', right: '---:', center: ':---:' };
let isCodeBlock_ = null;
function getAlignment(node) { function getAlignment(node) {
return node ? (node.getAttribute('align') || node.style.textAlign || '').toLowerCase() : ''; return node ? (node.getAttribute('align') || node.style.textAlign || '').toLowerCase() : '';
} }
@ -65,10 +67,10 @@ rules.tableRow = {
} }
rules.table = { rules.table = {
// Only convert tables with a heading row. // Only convert tables that can result in valid Markdown
// Tables with no heading row are kept using `keep` (see below). // Other tables are kept as HTML using `keep` (see below).
filter: function (node) { filter: function (node) {
return node.nodeName === 'TABLE' return node.nodeName === 'TABLE' && !tableShouldBeHtml(node);
}, },
replacement: function (content, node) { replacement: function (content, node) {
@ -154,6 +156,35 @@ function nodeContainsTable(node) {
return false; 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 // 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. // will be rendered one after the other as if they were paragraphs.
function tableShouldBeSkipped(tableNode) { function tableShouldBeSkipped(tableNode) {
@ -192,8 +223,11 @@ function tableColCount(node) {
} }
export default function tables (turndownService) { export default function tables (turndownService) {
isCodeBlock_ = turndownService.isCodeBlock;
turndownService.keep(function (node) { 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]) for (var key in rules) turndownService.addRule(key, rules[key])
} }

View File

@ -617,7 +617,9 @@ rules.mathjaxScriptBlock = {
rules.joplinHtmlInMarkdown = { rules.joplinHtmlInMarkdown = {
filter: function (node) { 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) { replacement: function (content, node) {

View File

@ -145,7 +145,12 @@ TurndownService.prototype = {
return escapes.reduce(function (accumulator, escape) { return escapes.reduce(function (accumulator, escape) {
return accumulator.replace(escape[0], escape[1]) return accumulator.replace(escape[0], escape[1])
}, string) }, string)
} },
isCodeBlock: function(node) {
return isCodeBlock(node);
},
} }
/** /**