diff --git a/.eslintignore b/.eslintignore index d6e70bf8f..da10fab99 100644 --- a/.eslintignore +++ b/.eslintignore @@ -228,6 +228,7 @@ packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js packages/app-desktop/gui/NoteEditor/commands/index.js +packages/app-desktop/gui/NoteEditor/commands/pasteAsText.js packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.js packages/app-desktop/gui/NoteEditor/commands/showRevisions.js packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.js diff --git a/.gitignore b/.gitignore index d4b3df803..8bbb1ed7a 100644 --- a/.gitignore +++ b/.gitignore @@ -216,6 +216,7 @@ packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js packages/app-desktop/gui/NoteEditor/commands/index.js +packages/app-desktop/gui/NoteEditor/commands/pasteAsText.js packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.js packages/app-desktop/gui/NoteEditor/commands/showRevisions.js packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.js diff --git a/packages/app-desktop/gui/MenuBar.tsx b/packages/app-desktop/gui/MenuBar.tsx index b99bb3af4..8e26a43b3 100644 --- a/packages/app-desktop/gui/MenuBar.tsx +++ b/packages/app-desktop/gui/MenuBar.tsx @@ -634,6 +634,7 @@ function useMenu(props: Props) { menuItemDic.textCopy, menuItemDic.textCut, menuItemDic.textPaste, + menuItemDic.pasteAsText, menuItemDic.textSelectAll, separator(), // Using the generic "undo"/"redo" roles mean the menu diff --git a/packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx b/packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx index b0c11de82..fefec1a3b 100644 --- a/packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx +++ b/packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx @@ -76,6 +76,16 @@ function stripMarkup(markupLanguage: number, markup: string, options: any = null return markupToHtml_.stripMarkup(markupLanguage, markup, options); } +function createSyntheticClipboardEventWithoutHTML(): ClipboardEvent { + const clipboardData = new DataTransfer(); + for (const format of clipboard.availableFormats()) { + if (format !== 'text/html') { + clipboardData.setData(format, clipboard.read(format)); + } + } + return new ClipboardEvent('paste', { clipboardData }); +} + interface TinyMceCommand { name: string; value?: any; @@ -260,6 +270,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => { // https://github.com/tinymce/tinymce/issues/3745 window.requestAnimationFrame(() => editor.undoManager.add()); }, + pasteAsText: () => editor.fire('pasteAsText'), }; if (additionalCommands[cmd.name]) { @@ -992,7 +1003,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => { } } - async function onPaste(event: any) { + async function onPaste(event: ClipboardEvent) { // We do not use the default pasting behaviour because the input has // to be processed in various ways. event.preventDefault(); @@ -1070,10 +1081,15 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => { } } + async function onPasteAsText() { + await onPaste(createSyntheticClipboardEventWithoutHTML()); + } + editor.on('keyup', onKeyUp); editor.on('keydown', onKeyDown); editor.on('keypress', onKeypress); editor.on('paste', onPaste); + editor.on('pasteAsText', onPasteAsText); editor.on('copy', onCopy); // `compositionend` means that a user has finished entering a Chinese // (or other languages that require IME) character. @@ -1090,6 +1106,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => { editor.off('keydown', onKeyDown); editor.off('keypress', onKeypress); editor.off('paste', onPaste); + editor.off('pasteAsText', onPasteAsText); editor.off('copy', onCopy); editor.off('compositionend', onChangeHandler); editor.off('cut', onCut); diff --git a/packages/app-desktop/gui/NoteEditor/commands/index.ts b/packages/app-desktop/gui/NoteEditor/commands/index.ts index e905970b0..129382cb6 100644 --- a/packages/app-desktop/gui/NoteEditor/commands/index.ts +++ b/packages/app-desktop/gui/NoteEditor/commands/index.ts @@ -1,12 +1,14 @@ // AUTO-GENERATED using `gulp buildCommandIndex` import * as focusElementNoteBody from './focusElementNoteBody'; import * as focusElementNoteTitle from './focusElementNoteTitle'; +import * as pasteAsText from './pasteAsText'; import * as showLocalSearch from './showLocalSearch'; import * as showRevisions from './showRevisions'; const index:any[] = [ focusElementNoteBody, focusElementNoteTitle, + pasteAsText, showLocalSearch, showRevisions, ]; diff --git a/packages/app-desktop/gui/NoteEditor/commands/pasteAsText.ts b/packages/app-desktop/gui/NoteEditor/commands/pasteAsText.ts new file mode 100644 index 000000000..52ee9e924 --- /dev/null +++ b/packages/app-desktop/gui/NoteEditor/commands/pasteAsText.ts @@ -0,0 +1,16 @@ +import { CommandRuntime, CommandDeclaration } from '@joplin/lib/services/CommandService'; +import { _ } from '@joplin/lib/locale'; + +export const declaration: CommandDeclaration = { + name: 'pasteAsText', + label: () => _('Paste as text'), +}; + +export const runtime = (comp: any): CommandRuntime => { + return { + execute: async () => { + comp.editorRef.current.execCommand({ name: 'pasteAsText' }); + }, + enabledCondition: 'oneNoteSelected && richTextEditorVisible', + }; +}; diff --git a/packages/app-desktop/gui/NoteEditor/utils/useWindowCommandHandler.ts b/packages/app-desktop/gui/NoteEditor/utils/useWindowCommandHandler.ts index 4c9df004c..cd4be46b3 100644 --- a/packages/app-desktop/gui/NoteEditor/utils/useWindowCommandHandler.ts +++ b/packages/app-desktop/gui/NoteEditor/utils/useWindowCommandHandler.ts @@ -9,6 +9,7 @@ const commandsWithDependencies = [ require('../commands/showLocalSearch'), require('../commands/focusElementNoteTitle'), require('../commands/focusElementNoteBody'), + require('../commands/pasteAsText'), ]; interface HookDependencies { diff --git a/packages/app-desktop/gui/menuCommandNames.ts b/packages/app-desktop/gui/menuCommandNames.ts index 009ced771..012444563 100644 --- a/packages/app-desktop/gui/menuCommandNames.ts +++ b/packages/app-desktop/gui/menuCommandNames.ts @@ -65,5 +65,6 @@ export default function() { 'switchProfile1', 'switchProfile2', 'switchProfile3', + 'pasteAsText', ]; } diff --git a/packages/lib/services/KeymapService.ts b/packages/lib/services/KeymapService.ts index 4a217b4f3..28c5cdb88 100644 --- a/packages/lib/services/KeymapService.ts +++ b/packages/lib/services/KeymapService.ts @@ -23,6 +23,7 @@ const defaultKeymapItems = { { accelerator: 'Cmd+C', command: 'textCopy' }, { accelerator: 'Cmd+X', command: 'textCut' }, { accelerator: 'Cmd+V', command: 'textPaste' }, + { accelerator: 'Cmd+Shift+V', command: 'pasteAsText' }, { accelerator: 'Cmd+A', command: 'textSelectAll' }, { accelerator: 'Cmd+B', command: 'textBold' }, { accelerator: 'Cmd+I', command: 'textItalic' }, @@ -67,6 +68,7 @@ const defaultKeymapItems = { { accelerator: 'Ctrl+C', command: 'textCopy' }, { accelerator: 'Ctrl+X', command: 'textCut' }, { accelerator: 'Ctrl+V', command: 'textPaste' }, + { accelerator: 'Ctrl+Shift+V', command: 'pasteAsText' }, { accelerator: 'Ctrl+A', command: 'textSelectAll' }, { accelerator: 'Ctrl+B', command: 'textBold' }, { accelerator: 'Ctrl+I', command: 'textItalic' },