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

Desktop: Speed up pasting text and images in Rich Text Editor

This commit is contained in:
Laurent Cozic 2024-02-08 12:51:31 +00:00
parent 4b7f0bfbb9
commit b1877fcd0d
3 changed files with 34 additions and 13 deletions

View File

@ -15,7 +15,6 @@ import useContextMenu from './utils/useContextMenu';
import { copyHtmlToClipboard } from '../../utils/clipboardUtils';
import shim from '@joplin/lib/shim';
import { MarkupLanguage, MarkupToHtml } from '@joplin/renderer';
import { reg } from '@joplin/lib/registry';
import BaseItem from '@joplin/lib/models/BaseItem';
import setupToolbarButtons from './utils/setupToolbarButtons';
import { plainTextToHtml } from '@joplin/lib/htmlUtils';
@ -31,10 +30,13 @@ import lightTheme from '@joplin/lib/themes/light';
import { Options as NoteStyleOptions } from '@joplin/renderer/noteStyle';
import markupRenderOptions from '../../utils/markupRenderOptions';
import { DropHandler } from '../../utils/useDropHandler';
import Logger from '@joplin/utils/Logger';
const md5 = require('md5');
const { clipboard } = require('electron');
const supportedLocales = require('./supportedLocales');
const logger = Logger.create('TinyMCE');
// In TinyMCE 5.2, when setting the body to '<div id="rendered-md"></div>',
// it would end up as '<div id="rendered-md"><br/></div>' once rendered
// (an additional <br/> was inserted).
@ -153,7 +155,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
if (anchor) {
anchor.scrollIntoView();
} else {
reg.logger().warn('TinyMce: could not find anchor with ID ', anchorName);
logger.warn('could not find anchor with ID ', anchorName);
}
} else {
props.onMessage({ channel: href });
@ -193,7 +195,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
execCommand: async (cmd: EditorCommand) => {
if (!editor) return false;
reg.logger().debug('TinyMce: execCommand', cmd);
logger.debug('execCommand', cmd);
let commandProcessed = true;
@ -215,7 +217,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
} else if (cmd.value.type === 'files') {
insertResourcesIntoContentRef.current(cmd.value.paths, { createFileURL: !!cmd.value.createFileURL });
} else {
reg.logger().warn('TinyMCE: unsupported drop item: ', cmd);
logger.warn('unsupported drop item: ', cmd);
}
} else {
commandProcessed = false;
@ -249,7 +251,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
}
if (!joplinCommandToTinyMceCommands[cmd.name]) {
reg.logger().warn('TinyMCE: unsupported Joplin command: ', cmd);
logger.warn('unsupported Joplin command: ', cmd);
return false;
}
@ -1132,16 +1134,20 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
const resourceMds = await getResourcesFromPasteEvent(event);
if (shouldPasteResources(pastedText, pastedHtml, resourceMds)) {
logger.info(`onPaste: pasting ${resourceMds.length} resources`);
if (resourceMds.length) {
const result = await markupToHtml.current(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, resourceMds.join('\n'), markupRenderOptions({ bodyOnly: true }));
editor.insertContent(result.html);
}
} else {
if (BaseItem.isMarkdownTag(pastedText)) { // Paste a link to a note
logger.info('onPaste: pasting as a Markdown tag');
const result = await markupToHtml.current(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, pastedText, markupRenderOptions({ bodyOnly: true }));
editor.insertContent(result.html);
} else { // Paste regular text
if (pastedHtml) { // Handles HTML
logger.info('onPaste: pasting as HTML');
const modifiedHtml = await processPastedHtml(
pastedHtml,
prop_htmlToMarkdownRef.current,
@ -1149,6 +1155,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
);
editor.insertContent(modifiedHtml);
} else { // Handles plain text
logger.info('onPaste: pasting as text');
pasteAsPlainText(pastedText);
}
}

View File

@ -25,7 +25,7 @@ export default (pastedText: string, pastedHtml: string, resourceMds: string[]) =
logger.info('Resources:', resourceMds);
if (pastedText) {
logger.info('Not pasting resources because the clipboard contains plain text');
logger.info('Not pasting resources only because the clipboard contains plain text');
return false;
}

View File

@ -152,8 +152,24 @@ export async function processPastedHtml(html: string, htmlToMd: HtmlToMarkdownHa
allImageUrls.push(src);
});
const downloadImage = async (imageSrc: string) => {
try {
const filePath = `${Setting.value('tempDir')}/${md5(Date.now() + Math.random())}`;
await shim.fetchBlob(imageSrc, { path: filePath });
const createdResource = await shim.createResourceFromPath(filePath);
await shim.fsDriver().remove(filePath);
mappedResources[imageSrc] = `file://${encodeURI(Resource.fullPath(createdResource))}`;
} catch (error) {
logger.warn(`Error creating a resource for ${imageSrc}.`, error);
mappedResources[imageSrc] = imageSrc;
}
};
const downloadImages: Promise<void>[] = [];
for (const imageSrc of allImageUrls) {
if (!mappedResources[imageSrc]) {
logger.info(`processPastedHtml: Processing image ${imageSrc}`);
try {
if (imageSrc.startsWith('file')) {
const imageFilePath = path.normalize(fileUriToPath(imageSrc));
@ -165,22 +181,20 @@ export async function processPastedHtml(html: string, htmlToMd: HtmlToMarkdownHa
const createdResource = await shim.createResourceFromPath(imageFilePath);
mappedResources[imageSrc] = `file://${encodeURI(Resource.fullPath(createdResource))}`;
}
} else if (imageSrc.startsWith('data:')) { // Data URIs
} else if (imageSrc.startsWith('data:')) {
mappedResources[imageSrc] = imageSrc;
} else {
const filePath = `${Setting.value('tempDir')}/${md5(Date.now() + Math.random())}`;
await shim.fetchBlob(imageSrc, { path: filePath });
const createdResource = await shim.createResourceFromPath(filePath);
await shim.fsDriver().remove(filePath);
mappedResources[imageSrc] = `file://${encodeURI(Resource.fullPath(createdResource))}`;
downloadImages.push(downloadImage(imageSrc));
}
} catch (error) {
logger.warn(`Error creating a resource for ${imageSrc}.`, error);
logger.warn(`processPastedHtml: Error creating a resource for ${imageSrc}.`, error);
mappedResources[imageSrc] = imageSrc;
}
}
}
await Promise.all(downloadImages);
// TinyMCE can accept any type of HTML, including HTML that may not be preserved once saved as
// Markdown. For example the content may have a dark background which would be supported by
// TinyMCE, but lost once the note is saved. So here we convert the HTML to Markdown then back