mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-21 09:38:01 +02:00
Desktop: Fixed pasting HTML in Rich Text editor, and improved pasting plain text
This commit is contained in:
parent
9e9bf63d70
commit
2226b79c46
@ -953,6 +953,9 @@ packages/lib/fs-driver-node.js.map
|
||||
packages/lib/htmlUtils.d.ts
|
||||
packages/lib/htmlUtils.js
|
||||
packages/lib/htmlUtils.js.map
|
||||
packages/lib/htmlUtils.test.d.ts
|
||||
packages/lib/htmlUtils.test.js
|
||||
packages/lib/htmlUtils.test.js.map
|
||||
packages/lib/import-enex-md-gen.d.ts
|
||||
packages/lib/import-enex-md-gen.js
|
||||
packages/lib/import-enex-md-gen.js.map
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -939,6 +939,9 @@ packages/lib/fs-driver-node.js.map
|
||||
packages/lib/htmlUtils.d.ts
|
||||
packages/lib/htmlUtils.js
|
||||
packages/lib/htmlUtils.js.map
|
||||
packages/lib/htmlUtils.test.d.ts
|
||||
packages/lib/htmlUtils.test.js
|
||||
packages/lib/htmlUtils.test.js.map
|
||||
packages/lib/import-enex-md-gen.d.ts
|
||||
packages/lib/import-enex-md-gen.js
|
||||
packages/lib/import-enex-md-gen.js.map
|
||||
|
@ -20,6 +20,7 @@ const taboverride = require('taboverride');
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
import BaseItem from '@joplin/lib/models/BaseItem';
|
||||
import setupToolbarButtons from './utils/setupToolbarButtons';
|
||||
import { plainTextToHtml } from '@joplin/lib/htmlUtils';
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
const { clipboard } = require('electron');
|
||||
const supportedLocales = require('./supportedLocales');
|
||||
@ -1037,6 +1038,10 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
}
|
||||
|
||||
async function onPaste(event: any) {
|
||||
// We do not use the default pasting behaviour because the input has
|
||||
// to be processed in various ways.
|
||||
event.preventDefault();
|
||||
|
||||
const resourceMds = await handlePasteEvent(event);
|
||||
if (resourceMds.length) {
|
||||
const result = await markupToHtml.current(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, resourceMds.join('\n'), markupRenderOptions({ bodyOnly: true }));
|
||||
@ -1045,23 +1050,25 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
const pastedText = event.clipboardData.getData('text/plain');
|
||||
|
||||
if (BaseItem.isMarkdownTag(pastedText)) { // Paste a link to a note
|
||||
event.preventDefault();
|
||||
const result = await markupToHtml.current(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, pastedText, markupRenderOptions({ bodyOnly: true }));
|
||||
editor.insertContent(result.html);
|
||||
} else { // Paste regular text
|
||||
// HACK: TinyMCE doesn't add an undo step when pasting, for unclear reasons
|
||||
// so we manually add it here. We also can't do it immediately it seems, or
|
||||
// else nothing is added to the stack, so do it on the next frame.
|
||||
|
||||
const pastedHtml = event.clipboardData.getData('text/html');
|
||||
if (pastedHtml) {
|
||||
event.preventDefault();
|
||||
if (pastedHtml) { // Handles HTML
|
||||
const modifiedHtml = await processPastedHtml(pastedHtml);
|
||||
editor.insertContent(modifiedHtml);
|
||||
} else { // Handles plain text
|
||||
pasteAsPlainText(pastedText);
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(() => editor.undoManager.add());
|
||||
onChangeHandler();
|
||||
// This code before was necessary to get undo working after
|
||||
// pasting but it seems it's no longer necessary, so
|
||||
// removing it for now. We also couldn't do it immediately
|
||||
// it seems, or else nothing is added to the stack, so do it
|
||||
// on the next frame.
|
||||
//
|
||||
// window.requestAnimationFrame(() =>
|
||||
// editor.undoManager.add()); onChangeHandler();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1080,6 +1087,13 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
onChangeHandler();
|
||||
}
|
||||
|
||||
function pasteAsPlainText(text: string = null) {
|
||||
const pastedText = text === null ? clipboard.readText() : text;
|
||||
if (pastedText) {
|
||||
editor.insertContent(plainTextToHtml(pastedText));
|
||||
}
|
||||
}
|
||||
|
||||
function onKeyDown(event: any) {
|
||||
// It seems "paste as text" is handled automatically by
|
||||
// on Windows so the code below so we need to run the below
|
||||
@ -1092,8 +1106,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
// it here and we don't need to do anything special in onPaste
|
||||
if (!shim.isWindows()) {
|
||||
if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.code === 'KeyV') {
|
||||
const pastedText = clipboard.readText();
|
||||
if (pastedText) editor.insertContent(pastedText);
|
||||
pasteAsPlainText();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -135,6 +135,13 @@ export async function processPastedHtml(html: string) {
|
||||
const allImageUrls: string[] = [];
|
||||
const mappedResources: Record<string, string> = {};
|
||||
|
||||
// When copying text from eg. GitHub, the HTML might contain non-breaking
|
||||
// spaces instead of regular spaces. If these non-breaking spaces are
|
||||
// inserted into the TinyMCE editor (using insertContent), they will be
|
||||
// dropped. So here we convert them to regular spaces.
|
||||
// https://stackoverflow.com/a/31790544/561309
|
||||
html = html.replace(/[\u202F\u00A0]/g, ' ');
|
||||
|
||||
htmlUtils.replaceImageUrls(html, (src: string) => {
|
||||
allImageUrls.push(src);
|
||||
});
|
||||
|
28
packages/lib/htmlUtils.test.ts
Normal file
28
packages/lib/htmlUtils.test.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { plainTextToHtml } from './htmlUtils';
|
||||
|
||||
describe('htmlUtils', () => {
|
||||
|
||||
test('should convert a plain text string to its HTML equivalent', () => {
|
||||
const testCases = [
|
||||
[
|
||||
'',
|
||||
'',
|
||||
],
|
||||
[
|
||||
'line 1\nline 2',
|
||||
'<p>line 1</p><p>line 2</p>',
|
||||
],
|
||||
[
|
||||
'<img onerror="http://downloadmalware.com"/>',
|
||||
'<img onerror="http://downloadmalware.com"/>',
|
||||
],
|
||||
];
|
||||
|
||||
for (const t of testCases) {
|
||||
const [input, expected] = t;
|
||||
const actual = plainTextToHtml(input);
|
||||
expect(actual).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
@ -2,6 +2,7 @@ const urlUtils = require('./urlUtils.js');
|
||||
const Entities = require('html-entities').AllHtmlEntities;
|
||||
const htmlentities = new Entities().encode;
|
||||
const htmlparser2 = require('@joplin/fork-htmlparser2');
|
||||
const { escapeHtml } = require('./string-utils.js');
|
||||
|
||||
// [\s\S] instead of . for multiline matching
|
||||
// https://stackoverflow.com/a/16119722/561309
|
||||
@ -153,3 +154,16 @@ class HtmlUtils {
|
||||
}
|
||||
|
||||
export default new HtmlUtils();
|
||||
|
||||
export function plainTextToHtml(plainText: string): string {
|
||||
const lines = plainText
|
||||
.replace(/[\n\r]/g, '\n')
|
||||
.split('\n');
|
||||
|
||||
const lineOpenTag = lines.length > 1 ? '<p>' : '';
|
||||
const lineCloseTag = lines.length > 1 ? '</p>' : '';
|
||||
|
||||
return lines
|
||||
.map(line => lineOpenTag + escapeHtml(line) + lineCloseTag)
|
||||
.join('');
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user