mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-02 12:47:41 +02:00
Desktop: Fixes #10061: Fix paste adds newlines in the Rich Text Editor when certain plugins are enabled (#10635)
This commit is contained in:
parent
e1abe0b4cb
commit
818f9f58d1
@ -6,7 +6,7 @@ import Resource from '@joplin/lib/models/Resource';
|
|||||||
const bridge = require('@electron/remote').require('./bridge').default;
|
const bridge = require('@electron/remote').require('./bridge').default;
|
||||||
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
|
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
|
||||||
import htmlUtils from '@joplin/lib/htmlUtils';
|
import htmlUtils from '@joplin/lib/htmlUtils';
|
||||||
import rendererHtmlUtils, { extractHtmlBody } from '@joplin/renderer/htmlUtils';
|
import rendererHtmlUtils, { extractHtmlBody, removeWrappingParagraphAndTrailingEmptyElements } from '@joplin/renderer/htmlUtils';
|
||||||
import Logger from '@joplin/utils/Logger';
|
import Logger from '@joplin/utils/Logger';
|
||||||
import { fileUriToPath } from '@joplin/utils/url';
|
import { fileUriToPath } from '@joplin/utils/url';
|
||||||
import { MarkupLanguage } from '@joplin/renderer';
|
import { MarkupLanguage } from '@joplin/renderer';
|
||||||
@ -220,6 +220,13 @@ export async function processPastedHtml(html: string, htmlToMd: HtmlToMarkdownHa
|
|||||||
if (htmlToMd && mdToHtml) {
|
if (htmlToMd && mdToHtml) {
|
||||||
const md = await htmlToMd(MarkupLanguage.Markdown, html, '');
|
const md = await htmlToMd(MarkupLanguage.Markdown, html, '');
|
||||||
html = (await mdToHtml(MarkupLanguage.Markdown, md, markupRenderOptions({ bodyOnly: true }))).html;
|
html = (await mdToHtml(MarkupLanguage.Markdown, md, markupRenderOptions({ bodyOnly: true }))).html;
|
||||||
|
|
||||||
|
// When plugins that add to the end of rendered content are installed, bodyOnly can
|
||||||
|
// fail to remove the wrapping paragraph. This works around that issue by removing
|
||||||
|
// the wrapping paragraph in more cases. See issue #10061.
|
||||||
|
if (!md.trim().includes('\n')) {
|
||||||
|
html = removeWrappingParagraphAndTrailingEmptyElements(html);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return extractHtmlBody(rendererHtmlUtils.sanitizeHtml(html, {
|
return extractHtmlBody(rendererHtmlUtils.sanitizeHtml(html, {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import htmlUtils, { extractHtmlBody, htmlDocIsImageOnly } from './htmlUtils';
|
import htmlUtils, { extractHtmlBody, htmlDocIsImageOnly, removeWrappingParagraphAndTrailingEmptyElements } from './htmlUtils';
|
||||||
|
|
||||||
describe('htmlUtils', () => {
|
describe('htmlUtils', () => {
|
||||||
|
|
||||||
@ -86,4 +86,14 @@ describe('htmlUtils', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
['<p>Test</p><div></div>', 'Test'],
|
||||||
|
['<p>Testing</p><p>A test</p>', '<p>Testing</p><p>A test</p>'],
|
||||||
|
['<p>Testing</p><hr/>', '<p>Testing</p><hr/>'],
|
||||||
|
['<p>Testing</p><div style="border: 2px solid red;"></div>', '<p>Testing</p><div style="border: 2px solid red;"></div>'],
|
||||||
|
['<p>Testing</p><style onload=""></style>', 'Testing'],
|
||||||
|
['<p>is</p>\n<style onload="console.log(\'test\')"></style>', 'is\n'],
|
||||||
|
])('should remove empty elements (case %#)', (before, expected) => {
|
||||||
|
expect(removeWrappingParagraphAndTrailingEmptyElements(before)).toBe(expected);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -424,6 +424,54 @@ export const extractHtmlBody = (html: string) => {
|
|||||||
return bodyFound ? output.join('') : html;
|
return bodyFound ? output.join('') : html;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const removeWrappingParagraphAndTrailingEmptyElements = (html: string) => {
|
||||||
|
if (!html.startsWith('<p>')) return html;
|
||||||
|
|
||||||
|
const stack: string[] = [];
|
||||||
|
const output: string[] = [];
|
||||||
|
let inFirstParagraph = true;
|
||||||
|
let canSimplify = true;
|
||||||
|
|
||||||
|
const parser = new htmlparser2.Parser({
|
||||||
|
onopentag: (name: string, attrs: Record<string, string>) => {
|
||||||
|
if (inFirstParagraph && stack.length > 0) {
|
||||||
|
output.push(makeHtmlTag(name, attrs));
|
||||||
|
} else if (!inFirstParagraph && attrs.style) {
|
||||||
|
canSimplify = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
stack.push(name);
|
||||||
|
},
|
||||||
|
ontext: (encodedText: string) => {
|
||||||
|
if (encodedText.trim() && !inFirstParagraph) {
|
||||||
|
canSimplify = false;
|
||||||
|
} else {
|
||||||
|
output.push(encodedText);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onclosetag: (name: string) => {
|
||||||
|
stack.pop();
|
||||||
|
if (stack.length === 0 && name === 'p') {
|
||||||
|
inFirstParagraph = false;
|
||||||
|
} else if (inFirstParagraph) {
|
||||||
|
if (isSelfClosingTag(name)) return;
|
||||||
|
output.push(`</${name}>`);
|
||||||
|
|
||||||
|
// Many elements, even if empty, can still be visible.
|
||||||
|
// For example, an <hr/>. Don't simplify if these elements
|
||||||
|
// are present.
|
||||||
|
} else if (!['div', 'style', 'span'].includes(name)) {
|
||||||
|
canSimplify = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
parser.write(html);
|
||||||
|
parser.end();
|
||||||
|
|
||||||
|
return canSimplify ? output.join('') : html;
|
||||||
|
};
|
||||||
|
|
||||||
export const htmlDocIsImageOnly = (html: string) => {
|
export const htmlDocIsImageOnly = (html: string) => {
|
||||||
let imageCount = 0;
|
let imageCount = 0;
|
||||||
let nonImageFound = false;
|
let nonImageFound = false;
|
||||||
|
Loading…
Reference in New Issue
Block a user