diff --git a/shared/editor/lib/uploadPlugin.ts b/shared/editor/lib/uploadPlugin.ts deleted file mode 100644 index 8d0a0af601..0000000000 --- a/shared/editor/lib/uploadPlugin.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { Plugin } from "prosemirror-state"; -import { getDataTransferFiles } from "../../utils/files"; -import insertFiles, { Options } from "../commands/insertFiles"; - -const uploadPlugin = (options: Options) => - new Plugin({ - props: { - handleDOMEvents: { - paste(view, event: ClipboardEvent): boolean { - if (!view.editable || !options.uploadFile) { - return false; - } - - if (!event.clipboardData) { - return false; - } - - // check if we actually pasted any files - const files = Array.prototype.slice - .call(event.clipboardData.items) - .filter((dt: DataTransferItem) => dt.kind !== "string") - .map((dt: DataTransferItem) => dt.getAsFile()) - .filter(Boolean); - - if (files.length === 0) { - return false; - } - - // When copying from Microsoft Office product the clipboard contains - // an image version of the content, check if there is also text and - // use that instead in this scenario. - const html = event.clipboardData.getData("text/html"); - - // Fallback to default paste behavior if the clipboard contains HTML - // Even if there is an image, it's likely to be a screenshot from eg - // Microsoft Suite / Apple Numbers – and not the original content. - if (html.length && !html.includes(" response.blob()) + .then((blob) => { + const fileName = fileNameFromUrl(imageSrc) ?? "pasted-image"; + const ext = extension(blob.type) ?? "png"; + const name = fileName.endsWith(`.${ext}`) + ? fileName + : `${fileName}.${ext}`; + + void insertFiles( + view, + event, + result.pos, + [ + new File([blob], name, { + type: blob.type, + }), + ], + options + ); + }); + } + + return false; + }, + }, + }, + }); + } +} diff --git a/shared/utils/files.ts b/shared/utils/files.ts index 02e0119f2a..43873d1a0d 100644 --- a/shared/utils/files.ts +++ b/shared/utils/files.ts @@ -25,15 +25,40 @@ export function bytesToHumanReadable(bytes: number | undefined) { } /** - * Get an array of File objects from a drag event + * Get an image URL from a drag or clipboard event * - * @param event The react or native drag event - * @returns An array of Files + * @param event The event to get the image from. + * @returns The URL of the image. + */ +export function getDataTransferImage( + event: React.DragEvent | DragEvent | ClipboardEvent +) { + const dt = + event instanceof ClipboardEvent ? event.clipboardData : event.dataTransfer; + const untrustedHTML = dt?.getData("text/html"); + + try { + return untrustedHTML + ? new DOMParser() + .parseFromString(untrustedHTML, "text/html") + .querySelector("img")?.src + : dt?.getData("url"); + } catch (e) { + return; + } +} + +/** + * Get an array of File objects from a drag or clipboard event + * + * @param event The event to get files from. + * @returns An array of files. */ export function getDataTransferFiles( - event: React.DragEvent | DragEvent + event: React.DragEvent | DragEvent | ClipboardEvent ): File[] { - const dt = event.dataTransfer; + const dt = + event instanceof ClipboardEvent ? event.clipboardData : event.dataTransfer; if (dt) { if ("files" in dt && dt.files.length) { diff --git a/shared/utils/urls.ts b/shared/utils/urls.ts index 7e6a5a3c1b..6860539736 100644 --- a/shared/utils/urls.ts +++ b/shared/utils/urls.ts @@ -12,6 +12,21 @@ export function cdnPath(path: string): string { return `${env.CDN_URL ?? ""}${path}`; } +/** + * Extracts the file name from a given url. + * + * @param url The url to extract the file name from. + * @returns The file name. + */ +export function fileNameFromUrl(url: string) { + try { + const parsed = new URL(url); + return parsed.pathname.split("/").pop(); + } catch (err) { + return; + } +} + /** * Returns true if the given string is a link to inside the application. * @@ -146,6 +161,12 @@ export function sanitizeUrl(url: string | null | undefined) { return url; } +/** + * Returns a regex to match the given url. + * + * @param url The url to create a regex for. + * @returns A regex to match the url. + */ export function urlRegex(url: string | null | undefined): RegExp | undefined { if (!url || !isUrl(url)) { return undefined;