Skip to content

Commit 99bedb8

Browse files
fix: External file drag/drop and copy/paste (#1052)
* Fixed external file drag/drop & copy/paste insertion and errors when using file extensions instead of MIME types * Implemented PR feedback
1 parent 4f842be commit 99bedb8

File tree

6 files changed

+71
-20
lines changed

6 files changed

+71
-20
lines changed

packages/core/src/api/parsers/handleFileInsertion.ts

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
22
import { PartialBlock } from "../../blocks/defaultBlocks";
3-
import { insertOrUpdateBlock } from "../../extensions/SuggestionMenu/getDefaultSlashMenuItems";
43
import {
54
BlockSchema,
65
FileBlockConfig,
76
InlineContentSchema,
87
StyleSchema,
98
} from "../../schema";
9+
import { getBlockInfoFromPos } from "../getBlockInfoFromPos";
1010
import { acceptedMIMETypes } from "./acceptedMIMETypes";
1111

12+
function checkFileExtensionsMatch(
13+
fileExtension1: string,
14+
fileExtension2: string
15+
) {
16+
if (!fileExtension1.startsWith(".") || !fileExtension2.startsWith(".")) {
17+
throw new Error(`The strings provided are not valid file extensions.`);
18+
}
19+
20+
return fileExtension1 === fileExtension2;
21+
}
22+
1223
function checkMIMETypesMatch(mimeType1: string, mimeType2: string) {
1324
const types1 = mimeType1.split("/");
1425
const types2 = mimeType2.split("/");
@@ -71,10 +82,24 @@ export async function handleFileInsertion<
7182
// Gets file block corresponding to MIME type.
7283
let fileBlockType = "file";
7384
for (const fileBlockConfig of fileBlockConfigs) {
74-
for (const mimeType of fileBlockConfig.fileBlockAcceptMimeTypes || []) {
75-
if (checkMIMETypesMatch(items[i].type, mimeType)) {
76-
fileBlockType = fileBlockConfig.type;
77-
break;
85+
for (const mimeType of fileBlockConfig.fileBlockAccept || []) {
86+
const isFileExtension = mimeType.startsWith(".");
87+
const file = items[i].getAsFile();
88+
89+
if (file) {
90+
if (
91+
(!isFileExtension &&
92+
file.type &&
93+
checkMIMETypesMatch(items[i].type, mimeType)) ||
94+
(isFileExtension &&
95+
checkFileExtensionsMatch(
96+
"." + file.name.split(".").pop(),
97+
mimeType
98+
))
99+
) {
100+
fileBlockType = fileBlockConfig.type;
101+
break;
102+
}
78103
}
79104
}
80105
}
@@ -83,16 +108,42 @@ export async function handleFileInsertion<
83108
if (file) {
84109
const updateData = await editor.uploadFile(file);
85110

86-
if (typeof updateData === "string") {
87-
const fileBlock = {
88-
type: fileBlockType,
89-
props: {
90-
name: file.name,
91-
url: updateData,
92-
},
93-
} as PartialBlock<BSchema, I, S>;
111+
const fileBlock =
112+
typeof updateData === "string"
113+
? ({
114+
type: fileBlockType,
115+
props: {
116+
name: file.name,
117+
url: updateData,
118+
},
119+
} as PartialBlock<BSchema, I, S>)
120+
: { type: fileBlockType, ...updateData };
121+
122+
if (event.type === "paste") {
123+
editor.insertBlocks(
124+
[fileBlock],
125+
editor.getTextCursorPosition().block,
126+
"after"
127+
);
128+
}
129+
130+
if (event.type === "drop") {
131+
const coords = {
132+
left: (event as DragEvent).clientX,
133+
top: (event as DragEvent).clientY,
134+
};
135+
136+
const pos = editor._tiptapEditor.view.posAtCoords(coords);
137+
if (!pos) {
138+
return;
139+
}
140+
141+
const blockInfo = getBlockInfoFromPos(
142+
editor._tiptapEditor.state.doc,
143+
pos.pos
144+
);
94145

95-
insertOrUpdateBlock(editor, fileBlock);
146+
editor.insertBlocks([fileBlock], blockInfo.id, "after");
96147
}
97148
}
98149
}

packages/core/src/blocks/AudioBlockContent/AudioBlockContent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export const audioBlockConfig = {
4343
propSchema: audioPropSchema,
4444
content: "none",
4545
isFileBlock: true,
46-
fileBlockAcceptMimeTypes: ["audio/*"],
46+
fileBlockAccept: ["audio/*"],
4747
} satisfies FileBlockConfig;
4848

4949
export const audioRender = (

packages/core/src/blocks/ImageBlockContent/ImageBlockContent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export const imageBlockConfig = {
4949
propSchema: imagePropSchema,
5050
content: "none",
5151
isFileBlock: true,
52-
fileBlockAcceptMimeTypes: ["image/*"],
52+
fileBlockAccept: ["image/*"],
5353
} satisfies FileBlockConfig;
5454

5555
export const imageRender = (

packages/core/src/blocks/VideoBlockContent/VideoBlockContent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export const videoBlockConfig = {
4949
propSchema: videoPropSchema,
5050
content: "none",
5151
isFileBlock: true,
52-
fileBlockAcceptMimeTypes: ["video/*"],
52+
fileBlockAccept: ["video/*"],
5353
} satisfies FileBlockConfig;
5454

5555
export const videoRender = (

packages/core/src/schema/blocks/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export type FileBlockConfig = {
5050
};
5151
content: "none";
5252
isFileBlock: true;
53-
fileBlockAcceptMimeTypes?: string[];
53+
fileBlockAccept?: string[];
5454
};
5555

5656
// BlockConfig contains the "schema" info about a Block type

packages/react/src/components/FilePanel/DefaultTabs/UploadTab.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ export const UploadTab = <
7676

7777
const config = editor.schema.blockSchema[block.type];
7878
const accept =
79-
config.isFileBlock && config.fileBlockAcceptMimeTypes?.length
80-
? config.fileBlockAcceptMimeTypes.join(",")
79+
config.isFileBlock && config.fileBlockAccept?.length
80+
? config.fileBlockAccept.join(",")
8181
: "*/*";
8282

8383
return (

0 commit comments

Comments
 (0)