diff --git a/src/core/ui/forms/MarkdownInput/MarkdownInput.tsx b/src/core/ui/forms/MarkdownInput/MarkdownInput.tsx index 719dddc7bf..c496d86b0a 100644 --- a/src/core/ui/forms/MarkdownInput/MarkdownInput.tsx +++ b/src/core/ui/forms/MarkdownInput/MarkdownInput.tsx @@ -8,8 +8,10 @@ import React, { useLayoutEffect, useRef, useState, + useMemo, } from 'react'; import { Box, useTheme } from '@mui/material'; +import { useTranslation } from 'react-i18next'; import { Editor, EditorContent, useEditor } from '@tiptap/react'; import StarterKit from '@tiptap/starter-kit'; import { InputBaseComponentProps } from '@mui/material/InputBase/InputBase'; @@ -25,6 +27,10 @@ import { Highlight } from '@tiptap/extension-highlight'; 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'; interface MarkdownInputProps extends InputBaseComponentProps { controlsVisible?: 'always' | 'focused'; @@ -55,10 +61,6 @@ const proseMirrorStyles = { '& img': { maxWidth: '100%' }, } as const; -const editorOptions: Partial = { - extensions: [StarterKit, ImageExtension, Link, Highlight, Iframe], -}; - export const MarkdownInput = memo( forwardRef( ( @@ -90,6 +92,97 @@ export const MarkdownInput = memo( setHtmlContent(String(content)); }; + const { t } = useTranslation(); + + const notify = useNotification(); + + const [uploadFile] = useUploadFileMutation({ + onCompleted: data => { + notify(t('components.file-upload.file-upload-success'), 'success'); + + editor?.commands.setImage({ src: data.uploadFileOnStorageBucket, alt: 'pasted-image' }); + }, + + onError: error => { + console.error(error.message); + }, + }); + + const storageConfig = useStorageConfigContext(); + + const editorOptions: Partial = useMemo( + () => ({ + extensions: [StarterKit, ImageExtension, Link, Highlight, Iframe], + editorProps: { + handlePaste: (_view: EditorView, event: ClipboardEvent) => { + const clipboardData = event.clipboardData; + const items = clipboardData?.items; + + if (!items) { + return false; // Allow default behavior if no items are found. + } + + const storageBucketId = storageConfig?.storageBucketId; + + if (!storageBucketId) { + return false; // Stop custom handling if storageBucketId is missing. + } + + let imageProcessed = false; + + for (const item of items) { + // Handle images first + if (!imageProcessed && 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; + } + } + + // Check for HTML content containing tags + if (!imageProcessed && item.kind === 'string' && item.type === 'text/html') { + const htmlContent = clipboardData.getData('text/html'); + + if (htmlContent.includes(' - + + {({ characterCount }) => typeof maxLength === 'undefined' || characterCount <= maxLength ? null : (