From 05adb06aeb593bc9ebb20ffac06ef7c4b071cead Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Mon, 11 May 2020 19:26:04 +0100 Subject: [PATCH] Desktop: WYSIWYG: Allow pasting images in editor --- .../NoteBody/AceEditor/AceEditor.tsx | 32 +++---------------- .../NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx | 30 ++++++++++------- .../gui/NoteEditor/utils/resourceHandling.ts | 28 ++++++++++++++++ 3 files changed, 51 insertions(+), 39 deletions(-) diff --git a/ElectronClient/gui/NoteEditor/NoteBody/AceEditor/AceEditor.tsx b/ElectronClient/gui/NoteEditor/NoteBody/AceEditor/AceEditor.tsx index b48ebcaf64..5275d97b7e 100644 --- a/ElectronClient/gui/NoteEditor/NoteBody/AceEditor/AceEditor.tsx +++ b/ElectronClient/gui/NoteEditor/NoteBody/AceEditor/AceEditor.tsx @@ -3,7 +3,7 @@ import { useState, useEffect, useRef, forwardRef, useCallback, useImperativeHand // eslint-disable-next-line no-unused-vars import { EditorCommand, NoteBodyEditorProps } from '../../utils/types'; -import { commandAttachFileToBody } from '../../utils/resourceHandling'; +import { commandAttachFileToBody, handlePasteEvent } from '../../utils/resourceHandling'; import { ScrollOptions, ScrollOptionTypes } from '../../utils/types'; import { textOffsetToCursorPosition, useScrollHandler, useRootWidth, usePrevious, lineLeftSpaces, selectionRangeCurrentLine, selectionRangePreviousLine, currentTextOffset, textOffsetSelection, selectedText, useSelectionRange } from './utils'; import useListIdent from './utils/useListIdent'; @@ -15,12 +15,9 @@ const AceEditorReact = require('react-ace').default; const { bridge } = require('electron').remote.require('./bridge'); const Note = require('lib/models/Note.js'); const { clipboard } = require('electron'); -const mimeUtils = require('lib/mime-utils.js').mime; const Setting = require('lib/models/Setting.js'); const NoteTextViewer = require('../../../NoteTextViewer.min'); const shared = require('lib/components/shared/note-screen-shared.js'); -const md5 = require('md5'); -const { shim } = require('lib/shim.js'); const Menu = bridge().Menu; const MenuItem = bridge().MenuItem; const markdownUtils = require('lib/markdownUtils'); @@ -332,29 +329,10 @@ function AceEditor(props: NoteBodyEditorProps, ref: any) { }, [editor, props.content, addListItem, wrapSelectionWithStrings, selectionRangeCurrentLine, aceEditor_change, setEditorPercentScroll, setViewerPercentScroll, resetScroll, renderedBody]); const onEditorPaste = useCallback(async (event: any = null) => { - const formats = clipboard.availableFormats(); - for (let i = 0; i < formats.length; i++) { - const format = formats[i].toLowerCase(); - const formatType = format.split('/')[0]; - - const position = currentTextOffset(editor, props.content); - - if (formatType === 'image') { - if (event) event.preventDefault(); - - const image = clipboard.readImage(); - - const fileExt = mimeUtils.toFileExtension(format); - const filePath = `${Setting.value('tempDir')}/${md5(Date.now())}.${fileExt}`; - - await shim.writeImageToFile(image, format, filePath); - const newBody = await commandAttachFileToBody(props.content, [filePath], { position }); - await shim.fsDriver().remove(filePath); - - aceEditor_change(newBody); - } - } - }, [editor, props.content, aceEditor_change]); + const resourceMds = await handlePasteEvent(event); + if (!resourceMds.length) return; + wrapSelectionWithStrings('', '', resourceMds.join('\n')); + }, [wrapSelectionWithStrings]); const onEditorKeyDown = useCallback((event: any) => { setLastKeys(prevLastKeys => { diff --git a/ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx b/ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx index ec5222ee4d..a475a580f5 100644 --- a/ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx +++ b/ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { useState, useEffect, useCallback, useRef, forwardRef, useImperativeHandle } from 'react'; import { ScrollOptions, ScrollOptionTypes, EditorCommand, NoteBodyEditorProps } from '../../utils/types'; -import { resourcesStatus, commandAttachFileToBody } from '../../utils/resourceHandling'; +import { resourcesStatus, commandAttachFileToBody, handlePasteEvent } from '../../utils/resourceHandling'; import useScroll from './utils/useScroll'; import { menuItems, ContextMenuOptions, ContextMenuItemType } from '../../utils/contextMenu'; const { MarkupToHtml } = require('lib/joplin-renderer'); @@ -879,18 +879,24 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => { } async function onPaste(event:any) { - const pastedText = event.clipboardData.getData('text'); - - 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 })); + const resourceMds = await handlePasteEvent(event); + if (resourceMds.length) { + const result = await markupToHtml.current(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, resourceMds.join('\n'), 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. - window.requestAnimationFrame(() => editor.undoManager.add()); - onChangeHandler(); + } else { + const pastedText = event.clipboardData.getData('text'); + + 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. + window.requestAnimationFrame(() => editor.undoManager.add()); + onChangeHandler(); + } } } diff --git a/ElectronClient/gui/NoteEditor/utils/resourceHandling.ts b/ElectronClient/gui/NoteEditor/utils/resourceHandling.ts index 1d3ac337b9..e7f2a1cfe2 100644 --- a/ElectronClient/gui/NoteEditor/utils/resourceHandling.ts +++ b/ElectronClient/gui/NoteEditor/utils/resourceHandling.ts @@ -7,6 +7,9 @@ const { bridge } = require('electron').remote.require('./bridge'); const ResourceFetcher = require('lib/services/ResourceFetcher.js'); const { reg } = require('lib/registry.js'); const joplinRendererUtils = require('lib/joplin-renderer').utils; +const { clipboard } = require('electron'); +const mimeUtils = require('lib/mime-utils.js').mime; +const md5 = require('md5'); export async function handleResourceDownloadMode(noteBody: string) { if (noteBody && Setting.value('sync.resourceDownloadMode') === 'auto') { @@ -97,3 +100,28 @@ export function resourcesStatus(resourceInfos: any) { } return joplinRendererUtils.resourceStatusName(lowestIndex); } + +export async function handlePasteEvent(event:any) { + const output = []; + const formats = clipboard.availableFormats(); + for (let i = 0; i < formats.length; i++) { + const format = formats[i].toLowerCase(); + const formatType = format.split('/')[0]; + + if (formatType === 'image') { + if (event) event.preventDefault(); + + const image = clipboard.readImage(); + + const fileExt = mimeUtils.toFileExtension(format); + const filePath = `${Setting.value('tempDir')}/${md5(Date.now())}.${fileExt}`; + + await shim.writeImageToFile(image, format, filePath); + const md = await commandAttachFileToBody('', [filePath]); + await shim.fsDriver().remove(filePath); + + output.push(md); + } + } + return output; +}