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:
parent
e4cb871c11
commit
832e9454c7
@ -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
1
.gitignore
vendored
@ -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
|
||||||
|
@ -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('--------------------------------------------');
|
||||||
|
@ -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>
|
|
@ -1,7 +0,0 @@
|
|||||||
```
|
|
||||||
def ma_fonction():
|
|
||||||
"""
|
|
||||||
C'est une super fonction
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
```
|
|
14
packages/app-cli/tests/html_to_md/table_with_blockquote.html
Normal file
14
packages/app-cli/tests/html_to_md/table_with_blockquote.html
Normal 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>
|
@ -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>
|
15
packages/app-cli/tests/html_to_md/table_with_code_1.html
Normal file
15
packages/app-cli/tests/html_to_md/table_with_code_1.html
Normal 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>
|
1
packages/app-cli/tests/html_to_md/table_with_code_1.md
Normal file
1
packages/app-cli/tests/html_to_md/table_with_code_1.md
Normal 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>
|
22
packages/app-cli/tests/html_to_md/table_with_code_2.html
Normal file
22
packages/app-cli/tests/html_to_md/table_with_code_2.html
Normal 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>
|
1
packages/app-cli/tests/html_to_md/table_with_code_2.md
Normal file
1
packages/app-cli/tests/html_to_md/table_with_code_2.md
Normal 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>
|
14
packages/app-cli/tests/html_to_md/table_with_heading.html
Normal file
14
packages/app-cli/tests/html_to_md/table_with_heading.html
Normal 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>
|
1
packages/app-cli/tests/html_to_md/table_with_heading.md
Normal file
1
packages/app-cli/tests/html_to_md/table_with_heading.md
Normal 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>
|
14
packages/app-cli/tests/html_to_md/table_with_hr.html
Normal file
14
packages/app-cli/tests/html_to_md/table_with_hr.html
Normal 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>
|
1
packages/app-cli/tests/html_to_md/table_with_hr.md
Normal file
1
packages/app-cli/tests/html_to_md/table_with_hr.md
Normal 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>
|
1
packages/app-cli/tests/html_to_md/table_with_list.html
Normal file
1
packages/app-cli/tests/html_to_md/table_with_list.html
Normal 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>
|
1
packages/app-cli/tests/html_to_md/table_with_list.md
Normal file
1
packages/app-cli/tests/html_to_md/table_with_list.md
Normal 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>
|
@ -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])
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user