diff --git a/packages/core/editor/src/components/Editor/Editor.tsx b/packages/core/editor/src/components/Editor/Editor.tsx index 40dccba3a..6c943d4be 100644 --- a/packages/core/editor/src/components/Editor/Editor.tsx +++ b/packages/core/editor/src/components/Editor/Editor.tsx @@ -169,7 +169,6 @@ const Editor = ({ navigator.clipboard.write([clipboardItem]).then(() => { const html = new DOMParser().parseFromString(htmlString, 'text/html'); - console.log('HTML copied\n', html.body); }); if (HOTKEYS.isCut(event)) { @@ -306,6 +305,42 @@ const Editor = ({ } }; + const handleClipboardContent = async (file: File) => { + const plugin = Object.keys(editor.plugins).find((plugin) => { + const pluginInstance = editor.plugins[plugin]; + const mimeTypes = pluginInstance.clipboardPasteOrDropRules?.mimeTypes; + if (!mimeTypes) return false; + return mimeTypes.includes(file.type); + }); + + if (!plugin) return; + + const pluginInstance = editor.plugins[plugin]; + const block = await pluginInstance.clipboardPasteOrDropRules?.handler(file, editor); + if (!block) return; + + const blockData = Blocks.buildBlockData({ type: pluginInstance.type, value: [block] }); + Blocks.insertBlock(editor, pluginInstance.type, { focus: true, blockData }); + }; + + const onPaste = (e: ClipboardEvent) => { + const items = e.clipboardData.items; + for (const item of items) { + const file = item.getAsFile(); + if (!file) continue; + handleClipboardContent(file); + } + }; + + const onDrop = (e: DragEvent) => { + const items = e.dataTransfer?.files; + if (!items) return; + for (const file of items) { + if (!file) continue; + handleClipboardContent(file); + } + }; + const editorStyles: CSSProperties = getEditorStyles({ ...style, userSelect: selectionBox.selection ? 'none' : 'auto', @@ -323,6 +358,8 @@ const Editor = ({ onBlur={onBlur} onCopy={onCopy} onCut={onCopy} + onPaste={onPaste} + onDrop={onDrop} > {selectionBoxRoot !== false && ( diff --git a/packages/core/editor/src/plugins/createYooptaPlugin.tsx b/packages/core/editor/src/plugins/createYooptaPlugin.tsx index ed926514f..c282121df 100644 --- a/packages/core/editor/src/plugins/createYooptaPlugin.tsx +++ b/packages/core/editor/src/plugins/createYooptaPlugin.tsx @@ -38,6 +38,7 @@ export class YooptaPlugin, TOpt const extendedOptions = { ...this.plugin.options, ...options }; const elements = { ...this.plugin.elements }; + const clipboardPasteOrDropRules = { ...this.plugin.clipboardPasteOrDropRules }; if (renders) { Object.keys(renders).forEach((elementType) => { @@ -84,6 +85,7 @@ export class YooptaPlugin, TOpt ...this.plugin, elements: elements, options: extendedOptions as PluginOptions, + clipboardPasteOrDropRules: clipboardPasteOrDropRules, }); } } diff --git a/packages/core/editor/src/plugins/types.ts b/packages/core/editor/src/plugins/types.ts index 54bf8523c..fee725cf2 100644 --- a/packages/core/editor/src/plugins/types.ts +++ b/packages/core/editor/src/plugins/types.ts @@ -64,6 +64,11 @@ export type EventHandlers = { ) => EditorEventHandlers[key] | void; }; +export type PluginClipboardPasteOrDropRules = { + mimeTypes: string[]; + handler: (file: File, editor: YooEditor) => Promise; +}; + export type PluginEventHandlerOptions = { hotkeys: HOTKEYS_TYPE; defaultBlock: YooptaBlockData; @@ -89,6 +94,7 @@ export type Plugin, TPluginOpti events?: PluginEvents; options?: PluginOptions; parsers?: Partial>; + clipboardPasteOrDropRules?: PluginClipboardPasteOrDropRules; }; export type PluginParsers = { diff --git a/packages/plugins/image/src/commands/index.ts b/packages/plugins/image/src/commands/index.ts index 4b39abceb..166d9a9d2 100644 --- a/packages/plugins/image/src/commands/index.ts +++ b/packages/plugins/image/src/commands/index.ts @@ -11,14 +11,14 @@ type InsertImageOptions = ImageElementOptions & { }; export type ImageCommands = { - buildImageElements: (editor: YooEditor, options?: Partial) => ImageElement; + buildImageElements: (editor?: YooEditor, options?: Partial) => ImageElement; insertImage: (editor: YooEditor, options?: Partial) => void; deleteImage: (editor: YooEditor, blockId: string) => void; updateImage: (editor: YooEditor, blockId: string, props: Partial) => void; }; export const ImageCommands: ImageCommands = { - buildImageElements: (editor: YooEditor, options = {}) => { + buildImageElements: (editor?: YooEditor, options = {}) => { const imageProps = { ...options.props, nodeType: 'void' }; return { id: generateId(), type: 'image', children: [{ text: '' }], props: imageProps as ImageElementProps }; }, diff --git a/packages/plugins/image/src/plugin/index.tsx b/packages/plugins/image/src/plugin/index.tsx index bdd1088cc..8a7257fc5 100644 --- a/packages/plugins/image/src/plugin/index.tsx +++ b/packages/plugins/image/src/plugin/index.tsx @@ -1,4 +1,4 @@ -import { generateId, SlateElement, YooptaPlugin } from '@yoopta/editor'; +import { generateId, SlateElement, YooptaPlugin, YooEditor } from '@yoopta/editor'; import { ImageCommands } from '../commands'; import { ImageElementMap, ImageElementProps, ImagePluginElements, ImagePluginOptions } from '../types'; import { ImageRender } from '../ui/Image'; @@ -37,6 +37,15 @@ const Image = new YooptaPlugin({ accept: 'image/png, image/jpeg, image/gif, image/webp', maxSizes: { maxWidth: 650, maxHeight: 550 }, }, + clipboardPasteOrDropRules: { + mimeTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'], + handler: async (file: File, editor: YooEditor) => { + const pluginOptions = editor.plugins.Image.options as ImagePluginOptions; + const data = await pluginOptions.onUpload(file); + const block = ImageCommands.buildImageElements(undefined, { props: data }); + return block; + }, + }, parsers: { html: { deserialize: { diff --git a/packages/plugins/video/src/commands/index.ts b/packages/plugins/video/src/commands/index.ts index 5849c9ab9..4a9a6079e 100644 --- a/packages/plugins/video/src/commands/index.ts +++ b/packages/plugins/video/src/commands/index.ts @@ -11,14 +11,14 @@ type InsertVideoOptions = VideoElementOptions & { }; export type VideoCommands = { - buildVideoElements: (editor: YooEditor, options?: Partial) => VideoElement; + buildVideoElements: (editor?: YooEditor, options?: Partial) => VideoElement; insertVideo: (editor: YooEditor, options?: Partial) => void; deleteVideo: (editor: YooEditor, blockId: string) => void; updateVideo: (editor: YooEditor, blockId: string, props: Partial) => void; }; export const VideoCommands: VideoCommands = { - buildVideoElements: (editor: YooEditor, options = {}) => { + buildVideoElements: (editor?: YooEditor, options = {}) => { const videoProps = { ...options.props, nodeType: 'void' }; return { id: generateId(), type: 'video', children: [{ text: '' }], props: videoProps as VideoElementProps }; }, diff --git a/packages/plugins/video/src/plugin/index.tsx b/packages/plugins/video/src/plugin/index.tsx index 63505cacd..a3105e783 100644 --- a/packages/plugins/video/src/plugin/index.tsx +++ b/packages/plugins/video/src/plugin/index.tsx @@ -1,4 +1,4 @@ -import { generateId, YooptaPlugin } from '@yoopta/editor'; +import { generateId, YooptaPlugin, YooEditor } from '@yoopta/editor'; import { VideoCommands } from '../commands'; import { VideoElementMap, VideoPluginOptions } from '../types'; import { VideoRender } from '../ui/Video'; @@ -45,6 +45,15 @@ const Video = new YooptaPlugin({ description: 'Upload from device, insert from Youtube, Vimeo, DailyMotion, Loom, Wistia', }, }, + clipboardPasteOrDropRules: { + mimeTypes: ['video/mp4', 'video/webm', 'video/ogg', 'video/quicktime'], + handler: async (file: File, editor: YooEditor) => { + const pluginOptions = editor.plugins.Video.options as VideoPluginOptions; + const data = await pluginOptions.onUpload(file); + const block = VideoCommands.buildVideoElements(undefined, { props: data }); + return block; + }, + }, commands: VideoCommands, parsers: { html: {