fix: pasting not working in firefox (#9947)

This commit is contained in:
David Luzar
2025-09-06 22:51:23 +02:00
committed by GitHub
parent 3bdaafe4b5
commit b9d27d308e
14 changed files with 445 additions and 261 deletions

View File

@@ -324,7 +324,13 @@ import {
isEraserActive,
isHandToolActive,
} from "../appState";
import { copyTextToSystemClipboard, parseClipboard } from "../clipboard";
import {
copyTextToSystemClipboard,
parseClipboard,
parseDataTransferEvent,
type ParsedDataTransferFile,
} from "../clipboard";
import { exportCanvas, loadFromBlob } from "../data";
import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library";
import { restore, restoreElements } from "../data/restore";
@@ -346,7 +352,6 @@ import {
generateIdFromFile,
getDataURL,
getDataURL_sync,
getFilesFromEvent,
ImageURLToFile,
isImageFileHandle,
isSupportedImageFile,
@@ -3070,7 +3075,7 @@ class App extends React.Component<AppProps, AppState> {
// TODO: Cover with tests
private async insertClipboardContent(
data: ClipboardData,
filesData: Awaited<ReturnType<typeof getFilesFromEvent>>,
dataTransferFiles: ParsedDataTransferFile[],
isPlainPaste: boolean,
) {
const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
@@ -3088,7 +3093,7 @@ class App extends React.Component<AppProps, AppState> {
}
// ------------------- Mixed content with no files -------------------
if (filesData.length === 0 && !isPlainPaste && data.mixedContent) {
if (dataTransferFiles.length === 0 && !isPlainPaste && data.mixedContent) {
await this.addElementsFromMixedContentPaste(data.mixedContent, {
isPlainPaste,
sceneX,
@@ -3109,9 +3114,7 @@ class App extends React.Component<AppProps, AppState> {
}
// ------------------- Images or SVG code -------------------
const imageFiles = filesData
.map((data) => data.file)
.filter((file): file is File => isSupportedImageFile(file));
const imageFiles = dataTransferFiles.map((data) => data.file);
if (imageFiles.length === 0 && data.text && !isPlainPaste) {
const trimmedText = data.text.trim();
@@ -3256,8 +3259,11 @@ class App extends React.Component<AppProps, AppState> {
// must be called in the same frame (thus before any awaits) as the paste
// event else some browsers (FF...) will clear the clipboardData
// (something something security)
const filesData = await getFilesFromEvent(event);
const data = await parseClipboard(event, isPlainPaste);
const dataTransferList = await parseDataTransferEvent(event);
const filesList = dataTransferList.getFiles();
const data = await parseClipboard(dataTransferList, isPlainPaste);
if (this.props.onPaste) {
try {
@@ -3269,7 +3275,8 @@ class App extends React.Component<AppProps, AppState> {
}
}
await this.insertClipboardContent(data, filesData, isPlainPaste);
await this.insertClipboardContent(data, filesList, isPlainPaste);
this.setActiveTool({ type: this.defaultSelectionTool }, true);
event?.preventDefault();
},
@@ -10479,12 +10486,13 @@ class App extends React.Component<AppProps, AppState> {
event,
this.state,
);
const dataTransferList = await parseDataTransferEvent(event);
// must be retrieved first, in the same frame
const filesData = await getFilesFromEvent(event);
const fileItems = dataTransferList.getFiles();
if (filesData.length === 1) {
const { file, fileHandle } = filesData[0];
if (fileItems.length === 1) {
const { file, fileHandle } = fileItems[0];
if (
file &&
@@ -10516,15 +10524,15 @@ class App extends React.Component<AppProps, AppState> {
}
}
const imageFiles = filesData
const imageFiles = fileItems
.map((data) => data.file)
.filter((file): file is File => isSupportedImageFile(file));
.filter((file) => isSupportedImageFile(file));
if (imageFiles.length > 0 && this.isToolSupported("image")) {
return this.insertImages(imageFiles, sceneX, sceneY);
}
const libraryJSON = event.dataTransfer.getData(MIME_TYPES.excalidrawlib);
const libraryJSON = dataTransferList.getData(MIME_TYPES.excalidrawlib);
if (libraryJSON && typeof libraryJSON === "string") {
try {
const libraryItems = parseLibraryJSON(libraryJSON);
@@ -10539,16 +10547,18 @@ class App extends React.Component<AppProps, AppState> {
return;
}
if (filesData.length > 0) {
const { file, fileHandle } = filesData[0];
if (fileItems.length > 0) {
const { file, fileHandle } = fileItems[0];
if (file) {
// Attempt to parse an excalidraw/excalidrawlib file
await this.loadFileToCanvas(file, fileHandle);
}
}
if (event.dataTransfer?.types?.includes("text/plain")) {
const text = event.dataTransfer?.getData("text");
const textItem = dataTransferList.findByType(MIME_TYPES.text);
if (textItem) {
const text = textItem.value;
if (
text &&
embeddableURLValidator(text, this.props.validateEmbeddable) &&