From 612d72d7658a87dde1838770c8c129db8f594c47 Mon Sep 17 00:00:00 2001 From: Henry Heino <46334387+personalizedrefrigerator@users.noreply.github.com> Date: Wed, 30 Oct 2024 14:09:59 -0700 Subject: [PATCH] Desktop: Fixes #11274: Fix content dropped into the Markdown editor is missing a cursor preview or dropped at the wrong location (#11289) --- .../CodeMirror/v6/useEditorCommands.ts | 23 +++++++++----- .../app-desktop/gui/NoteEditor/utils/types.ts | 16 ++++++++++ .../gui/NoteEditor/utils/useDropHandler.ts | 30 +++++++++++++------ packages/editor/CodeMirror/createEditor.ts | 3 ++ packages/editor/types.ts | 1 + 5 files changed, 57 insertions(+), 16 deletions(-) diff --git a/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/useEditorCommands.ts b/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/useEditorCommands.ts index ab2e7a323..e6983409d 100644 --- a/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/useEditorCommands.ts +++ b/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/useEditorCommands.ts @@ -1,10 +1,10 @@ import { RefObject, useMemo } from 'react'; -import { CommandValue } from '../../../utils/types'; +import { CommandValue, DropCommandValue } from '../../../utils/types'; import { commandAttachFileToBody } from '../../../utils/resourceHandling'; import { _ } from '@joplin/lib/locale'; import dialogs from '../../../../dialogs'; -import { EditorCommandType } from '@joplin/editor/types'; +import { EditorCommandType, UserEventSource } from '@joplin/editor/types'; import Logger from '@joplin/utils/Logger'; import CodeMirrorControl from '@joplin/editor/CodeMirror/CodeMirrorControl'; import { MarkupLanguage } from '@joplin/renderer'; @@ -38,13 +38,22 @@ const useEditorCommands = (props: Props) => { }; return { - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied - dropItems: async (cmd: any) => { + dropItems: async (cmd: DropCommandValue) => { + let pos = cmd.pos && editorRef.current.editor.posAtCoords({ x: cmd.pos.clientX, y: cmd.pos.clientY }); if (cmd.type === 'notes') { - editorRef.current.insertText(cmd.markdownTags.join('\n')); + const text = cmd.markdownTags.join('\n'); + if ((pos ?? null) !== null) { + editorRef.current.select(pos, pos); + } + + editorRef.current.insertText(text, UserEventSource.Drop); } else if (cmd.type === 'files') { - const pos = props.selectionRange.from; - const newBody = await commandAttachFileToBody(props.editorContent, cmd.paths, { createFileURL: !!cmd.createFileURL, position: pos, markupLanguage: props.contentMarkupLanguage }); + pos ??= props.selectionRange.from; + const newBody = await commandAttachFileToBody(props.editorContent, cmd.paths, { + createFileURL: !!cmd.createFileURL, + position: pos, + markupLanguage: props.contentMarkupLanguage, + }); editorRef.current.updateBody(newBody); } else { logger.warn('CodeMirror: unsupported drop item: ', cmd); diff --git a/packages/app-desktop/gui/NoteEditor/utils/types.ts b/packages/app-desktop/gui/NoteEditor/utils/types.ts index 1f261661a..7d17bf460 100644 --- a/packages/app-desktop/gui/NoteEditor/utils/types.ts +++ b/packages/app-desktop/gui/NoteEditor/utils/types.ts @@ -252,3 +252,19 @@ export interface CommandValue { // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied value?: any; // For TinyMCE only } + +type DropCommandBase = { + pos: { + clientX: number; + clientY: number; + }|undefined; +}; + +export type DropCommandValue = ({ + type: 'notes'; + markdownTags: string[]; +}|{ + type: 'files'; + paths: string[]; + createFileURL: boolean; +}) & DropCommandBase; diff --git a/packages/app-desktop/gui/NoteEditor/utils/useDropHandler.ts b/packages/app-desktop/gui/NoteEditor/utils/useDropHandler.ts index cbf415416..2b292d9d3 100644 --- a/packages/app-desktop/gui/NoteEditor/utils/useDropHandler.ts +++ b/packages/app-desktop/gui/NoteEditor/utils/useDropHandler.ts @@ -1,6 +1,7 @@ import { useCallback } from 'react'; import Note from '@joplin/lib/models/Note'; import { DragEvent as ReactDragEvent } from 'react'; +import { DropCommandValue } from './types'; interface HookDependencies { // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied @@ -19,6 +20,11 @@ export default function useDropHandler(dependencies: HookDependencies): DropHand const dt = event.dataTransfer; const createFileURL = event.altKey; + const eventPosition = { + clientX: event.clientX, + clientY: event.clientY, + }; + if (dt.types.indexOf('text/x-jop-note-ids') >= 0) { const noteIds = JSON.parse(dt.getData('text/x-jop-note-ids')); @@ -29,12 +35,15 @@ export default function useDropHandler(dependencies: HookDependencies): DropHand noteMarkdownTags.push(Note.markdownTag(note)); } + const props: DropCommandValue = { + type: 'notes', + pos: eventPosition, + markdownTags: noteMarkdownTags, + }; + editorRef.current.execCommand({ name: 'dropItems', - value: { - type: 'notes', - markdownTags: noteMarkdownTags, - }, + value: props, }); }; void dropNotes(); @@ -51,13 +60,16 @@ export default function useDropHandler(dependencies: HookDependencies): DropHand paths.push(file.path); } + const props: DropCommandValue = { + type: 'files', + pos: eventPosition, + paths: paths, + createFileURL: createFileURL, + }; + editorRef.current.execCommand({ name: 'dropItems', - value: { - type: 'files', - paths: paths, - createFileURL: createFileURL, - }, + value: props, }); return true; } diff --git a/packages/editor/CodeMirror/createEditor.ts b/packages/editor/CodeMirror/createEditor.ts index cd03786a2..0fb27e8ff 100644 --- a/packages/editor/CodeMirror/createEditor.ts +++ b/packages/editor/CodeMirror/createEditor.ts @@ -6,6 +6,7 @@ import { classHighlighter } from '@lezer/highlight'; import { EditorView, drawSelection, highlightSpecialChars, ViewUpdate, Command, rectangularSelection, + dropCursor, } from '@codemirror/view'; import { history, undoDepth, redoDepth, standardKeymap } from '@codemirror/commands'; @@ -253,6 +254,8 @@ const createEditor = ( // Apply styles to entire lines (block-display decorations) decoratorExtension, + dropCursor(), + biDirectionalTextExtension, // Adds additional CSS classes to tokens (the default CSS classes are diff --git a/packages/editor/types.ts b/packages/editor/types.ts index 2c468b08b..b25bbd0a3 100644 --- a/packages/editor/types.ts +++ b/packages/editor/types.ts @@ -90,6 +90,7 @@ export interface ContentScriptData { // Intended to correspond with https://codemirror.net/docs/ref/#state.Transaction%5EuserEvent export enum UserEventSource { Paste = 'input.paste', + Drop = 'input.drop', } export interface EditorControl {