From 15b073cb8792950383bbe83da62c2c2932cecf45 Mon Sep 17 00:00:00 2001 From: Petar Kolev <33326233+reactoholic@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:30:15 +0200 Subject: [PATCH] fix paste issue (#7493) --- .../MarkdownInput/FormikMarkdownField.tsx | 5 +- .../ui/forms/MarkdownInput/MarkdownInput.tsx | 133 +++++++++--------- 2 files changed, 72 insertions(+), 66 deletions(-) diff --git a/src/core/ui/forms/MarkdownInput/FormikMarkdownField.tsx b/src/core/ui/forms/MarkdownInput/FormikMarkdownField.tsx index b479831385..643d275e7b 100644 --- a/src/core/ui/forms/MarkdownInput/FormikMarkdownField.tsx +++ b/src/core/ui/forms/MarkdownInput/FormikMarkdownField.tsx @@ -11,6 +11,7 @@ import { MarkdownTextMaxLength } from '../field-length.constants'; import { error as logError } from '@/core/logging/sentry/log'; import { isMarkdownMaxLengthError } from './MarkdownValidator'; import { useTranslation } from 'react-i18next'; +import { useStorageConfigContext } from '@/domain/storage/StorageBucket/StorageConfigContext'; interface MarkdownFieldProps extends InputProps { title: string; @@ -120,6 +121,8 @@ export const FormikMarkdownField = ({ const labelOffset = inputElement?.getLabelOffset(); + const storageConfig = useStorageConfigContext(); + return ( @@ -141,7 +144,7 @@ export const FormikMarkdownField = ({ onChange={handleChange} onBlur={handleBlur} label={title} - inputComponent={MarkdownInput} + inputComponent={props => } inputRef={inputRef} inputProps={{ controlsVisible, diff --git a/src/core/ui/forms/MarkdownInput/MarkdownInput.tsx b/src/core/ui/forms/MarkdownInput/MarkdownInput.tsx index 10cc834e50..0871f093fa 100644 --- a/src/core/ui/forms/MarkdownInput/MarkdownInput.tsx +++ b/src/core/ui/forms/MarkdownInput/MarkdownInput.tsx @@ -28,7 +28,6 @@ import { Selection } from 'prosemirror-state'; import { EditorOptions } from '@tiptap/core'; import { Iframe } from '../MarkdownInputControls/InsertEmbedCodeButton/Iframe'; import { EditorView } from '@tiptap/pm/view'; -import { useStorageConfigContext } from '@/domain/storage/StorageBucket/StorageConfigContext'; import { useUploadFileMutation } from '@/core/apollo/generated/apollo-hooks'; import { useNotification } from '../../notifications/useNotification'; @@ -37,6 +36,7 @@ interface MarkdownInputProps extends InputBaseComponentProps { maxLength?: number; hideImageOptions?: boolean; temporaryLocation?: boolean; + storageBucketId: string | undefined; } type Offset = { @@ -72,6 +72,7 @@ export const MarkdownInput = memo( hideImageOptions, onFocus, onBlur, + storageBucketId, temporaryLocation = false, }, ref @@ -121,85 +122,87 @@ export const MarkdownInput = memo( return false; // Not an image or HTML with images }; - const storageConfig = useStorageConfigContext(); + /** + * Handles the paste event in the editor. + * + * @param _view - The editor view instance. + * @param event - The clipboard event triggered by pasting. + * @returns {boolean} - Returns true if the paste event is handled, otherwise false - continue execution of the default. + * + * Reference to alternative way of handling paste events in Tiptap: https://tiptap.dev/docs/editor/extensions/functionality/filehandler + * + */ + const handlePaste = useCallback( + (_view: EditorView, event: ClipboardEvent): boolean => { + const clipboardData = event.clipboardData; + const items = clipboardData?.items; + + if (!items) { + return false; + } - const editorOptions: Partial = useMemo( - () => ({ - extensions: [StarterKit, ImageExtension, Link, Highlight, Iframe], - editorProps: { - /** - * Handles the paste event in the editor. - * - * @param _view - The editor view instance. - * @param event - The clipboard event triggered by pasting. - * @returns {boolean} - Returns true if the paste event is handled, otherwise false - continue execution of the default. - */ - handlePaste: (_view: EditorView, event: ClipboardEvent): boolean => { - const clipboardData = event.clipboardData; - const items = clipboardData?.items; - - if (!items) { - return false; - } + if (!storageBucketId) { + return false; + } - const storageBucketId = storageConfig?.storageBucketId; + let imageProcessed = false; - if (!storageBucketId) { - return false; - } + for (const item of items) { + const isImage = isImageOrHtmlWithImage(item, clipboardData); - let imageProcessed = false; + if (hideImageOptions && isImage) { + event.preventDefault(); - for (const item of items) { - const isImage = isImageOrHtmlWithImage(item, clipboardData); + return true; // Block paste of images or HTML with images + } - if (hideImageOptions && isImage) { - event.preventDefault(); + if (!imageProcessed && isImage) { + if (item.kind === 'file' && item.type.startsWith('image/')) { + const file = item.getAsFile(); - return true; // Block paste of images or HTML with images - } + if (file) { + const reader = new FileReader(); - if (!imageProcessed && isImage) { - if (item.kind === 'file' && item.type.startsWith('image/')) { - const file = item.getAsFile(); - - if (file) { - const reader = new FileReader(); - - reader.onload = () => { - uploadFile({ - variables: { - file, - uploadData: { storageBucketId, temporaryLocation }, - }, - }); - }; - - reader.readAsDataURL(file); - imageProcessed = true; - } - } else if (item.kind === 'string' && item.type === 'text/html') { - imageProcessed = true; // HTML with images - } - } + reader.onload = () => { + uploadFile({ + variables: { + file, + uploadData: { storageBucketId, temporaryLocation }, + }, + }); + }; - if (imageProcessed) { - // Stop if we have already processed an image - break; + reader.readAsDataURL(file); + imageProcessed = true; } + } else if (item.kind === 'string' && item.type === 'text/html') { + imageProcessed = true; // HTML with images } + } - if (imageProcessed) { - event.preventDefault(); + if (imageProcessed) { + // Stop if we have already processed an image + break; + } + } - return true; // Block default behavior for images - } + if (imageProcessed) { + event.preventDefault(); - return false; // Allow default behavior for text - }, - }, + return true; // Block default behavior for images + } + + return false; // Allow default behavior for text + }, + [storageBucketId, hideImageOptions, temporaryLocation, uploadFile, isImageOrHtmlWithImage] + ); + + const editorOptions: Partial = useMemo( + () => ({ + extensions: [StarterKit, ImageExtension, Link, Highlight, Iframe], + editorProps: { handlePaste }, }), - [storageConfig, temporaryLocation, uploadFile] + [handlePaste] ); const editor = useEditor({ ...editorOptions, content: htmlContent }, [htmlContent]);