diff --git a/src/commands/command-orchestrator.ts b/src/commands/command-orchestrator.ts index 22e53d4..2f6d036 100644 --- a/src/commands/command-orchestrator.ts +++ b/src/commands/command-orchestrator.ts @@ -143,7 +143,7 @@ export class CommandOrchestrator { return this.executeCommand( this.pasteOptions.command || getDefaultSaveImageCommandName(), { - saveImage: this.pasteOptions.saveImage, + pasteOptions: this.pasteOptions, event: event } as PasteCommandContext ); @@ -158,7 +158,7 @@ export class CommandOrchestrator { return this.executeCommand( this.pasteOptions.command || getDefaultSaveImageCommandName(), { - saveImage: this.pasteOptions.saveImage, + pasteOptions: this.pasteOptions, event: event } as PasteCommandContext ); @@ -173,7 +173,7 @@ export class CommandOrchestrator { return this.executeCommand( this.pasteOptions.command || getDefaultSaveImageCommandName(), { - saveImage: this.pasteOptions.saveImage, + pasteOptions: this.pasteOptions, event: event } as PasteCommandContext ); diff --git a/src/commands/command.ts b/src/commands/command.ts index db995dd..bca54a7 100644 --- a/src/commands/command.ts +++ b/src/commands/command.ts @@ -31,7 +31,7 @@ export interface CommandContext { export interface PasteCommandContext extends CommandContext { type: "paste"; event: React.ClipboardEvent | React.DragEvent | React.ChangeEvent; - saveImage: SaveImageHandler; + pasteOptions: PasteOptions; } export type ToolbarCommands = string[][]; diff --git a/src/commands/default-commands/save-image-command.tsx b/src/commands/default-commands/save-image-command.tsx index ea8c49a..e5255ed 100644 --- a/src/commands/default-commands/save-image-command.tsx +++ b/src/commands/default-commands/save-image-command.tsx @@ -2,7 +2,8 @@ import { Command, CommandContext, ExecuteOptions, - PasteCommandContext + PasteCommandContext, + PasteOptions } from "../command"; import { readFileAsync } from "../../util/files"; import { getBreaksNeededForEmptyLineBefore } from "../../util/MarkdownUtil"; @@ -26,6 +27,42 @@ function fileListToArray(list: FileList): Array { return result; } +function filterItems( + items: File[], + { multiple, accept }: Pick +): File[] { + let filteredItems = items; + + if (!multiple) { + filteredItems = filteredItems.slice(0, 1); + } + + if (accept) { + //https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers + const acceptedTypes = accept.split(","); + const fileExtensions = new Set( + acceptedTypes.filter(t => /^\.\w+/.test(t)).map(t => t.split(".")[1]) + ); + const mimeTypes = new Set( + acceptedTypes.filter(t => /^[-\w.]+\/[-\w.]+$/.test(t)) + ); + const anyTypes = new Set( + acceptedTypes + .filter(t => /(audio|video|image)\/\*/.test(t)) + .map(t => t.split("/")[0]) + ); + + filteredItems = filteredItems.filter( + f => + fileExtensions.has(f.name.split(".")[1]) || + mimeTypes.has(f.type) || + anyTypes.has(f.type.split("/")[0]) + ); + } + + return filteredItems; +} + export const saveImageCommand: Command = { async execute({ initialState, @@ -37,7 +74,10 @@ export const saveImageCommand: Command = { throw new Error("wrong context"); } const pasteContext = context as PasteCommandContext; - const { event, saveImage } = pasteContext; + const { + event, + pasteOptions: { saveImage, multiple, accept } + } = pasteContext; const items = isPasteEvent(context) ? dataTransferToArray((event as React.ClipboardEvent).clipboardData.items) @@ -47,7 +87,9 @@ export const saveImageCommand: Command = { (event as React.ChangeEvent).target.files ); - for (const index in items) { + const filteredItems = filterItems(items, { multiple, accept }); + + for (const index in filteredItems) { const initialState = textApi.getState(); const breaksBeforeCount = getBreaksNeededForEmptyLineBefore( initialState.text, diff --git a/src/components/ReactMde.tsx b/src/components/ReactMde.tsx index 1bdb089..fb4fd85 100644 --- a/src/components/ReactMde.tsx +++ b/src/components/ReactMde.tsx @@ -73,6 +73,14 @@ export interface ReactMdeState { editorHeight: number; } +const pasteOptionDefaults: Required> = { + accept: "image/*", + multiple: false +}; + export class ReactMde extends React.Component { /** * "finalRefs" is a clone of "props.refs" except that undefined refs are set to default values @@ -110,6 +118,8 @@ export class ReactMde extends React.Component { this.finalRefs.textarea, this.props.l18n, this.props.paste + ? { ...pasteOptionDefaults, ...this.props.paste } + : undefined ); const minEditorHeight = Math.min( props.maxEditorHeight, @@ -243,8 +253,10 @@ export class ReactMde extends React.Component { {l18n.pasteDropSelect}