diff --git a/src/components/PostEditor/PostTextarea/FileHandler.ts b/src/components/PostEditor/PostTextarea/ClipboardAndDropHandler.ts similarity index 72% rename from src/components/PostEditor/PostTextarea/FileHandler.ts rename to src/components/PostEditor/PostTextarea/ClipboardAndDropHandler.ts index 1e6b261b..25bc8da0 100644 --- a/src/components/PostEditor/PostTextarea/FileHandler.ts +++ b/src/components/PostEditor/PostTextarea/ClipboardAndDropHandler.ts @@ -11,14 +11,14 @@ const DRAGOVER_CLASS_LIST = [ 'rounded-md' ] -export interface FileHandlerOptions { +export interface ClipboardAndDropHandlerOptions { onUploadStart?: (file: File) => void onUploadSuccess?: (file: File, result: any) => void onUploadError?: (file: File, error: any) => void } -export const FileHandler = Extension.create({ - name: 'fileHandler', +export const ClipboardAndDropHandler = Extension.create({ + name: 'clipboardAndDropHandler', addOptions() { return { @@ -61,17 +61,38 @@ export const FileHandler = Extension.create({ }, handlePaste(view, event) { const items = Array.from(event.clipboardData?.items ?? []) - const mediaItem = items.find( - (item) => item.type.includes('image') || item.type.includes('video') - ) - if (!mediaItem) return false + let handled = false - const file = mediaItem.getAsFile() - if (!file) return false + for (const item of items) { + if ( + item.kind === 'file' && + (item.type.includes('image') || item.type.includes('video')) + ) { + const file = item.getAsFile() + if (file) { + uploadFile(view, file, options) + handled = true + } + } else if (item.kind === 'string' && item.type === 'text/plain') { + item.getAsString((text) => { + const { schema } = view.state + const parts = text.split('\n') + const nodes = [] + for (let i = 0; i < parts.length; i++) { + if (i > 0) nodes.push(schema.nodes.hardBreak.create()) + if (parts[i]) nodes.push(schema.text(parts[i])) + } + const fragment = schema.nodes.paragraph.create(null, nodes) + const tr = view.state.tr.replaceSelectionWith(fragment) + view.dispatch(tr) + }) + handled = true + } - uploadFile(view, file, options) - - return true + // Only handle the first file/string item + if (handled) break + } + return handled } } }) @@ -79,7 +100,7 @@ export const FileHandler = Extension.create({ } }) -async function uploadFile(view: EditorView, file: File, options: FileHandlerOptions) { +async function uploadFile(view: EditorView, file: File, options: ClipboardAndDropHandlerOptions) { const name = file.name options.onUploadStart?.(file) diff --git a/src/components/PostEditor/PostTextarea/index.tsx b/src/components/PostEditor/PostTextarea/index.tsx index c736754d..5836486d 100644 --- a/src/components/PostEditor/PostTextarea/index.tsx +++ b/src/components/PostEditor/PostTextarea/index.tsx @@ -2,6 +2,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { parseEditorJsonToText } from '@/lib/tiptap' import postContentCache from '@/services/post-content-cache.service' import Document from '@tiptap/extension-document' +import { HardBreak } from '@tiptap/extension-hard-break' import History from '@tiptap/extension-history' import Paragraph from '@tiptap/extension-paragraph' import Placeholder from '@tiptap/extension-placeholder' @@ -12,8 +13,8 @@ import { Event } from 'nostr-tools' import { Dispatch, forwardRef, SetStateAction, useImperativeHandle } from 'react' import { useTranslation } from 'react-i18next' import { usePostEditor } from '../PostEditorProvider' +import { ClipboardAndDropHandler } from './ClipboardAndDropHandler' import CustomMention from './CustomMention' -import { FileHandler } from './FileHandler' import Preview from './Preview' import suggestion from './suggestion' @@ -40,13 +41,14 @@ const PostTextarea = forwardRef< Paragraph, Text, History, + HardBreak, Placeholder.configure({ placeholder: t('Write something...') + ' (' + t('Paste or drop media files to upload') + ')' }), CustomMention.configure({ suggestion }), - FileHandler.configure({ + ClipboardAndDropHandler.configure({ onUploadStart: () => setUploadingFiles((prev) => prev + 1), onUploadSuccess: () => setUploadingFiles((prev) => prev - 1), onUploadError: () => setUploadingFiles((prev) => prev - 1)