@blocknote/core
Version:
A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.
190 lines (164 loc) • 5.19 kB
text/typescript
import { Block, PartialBlock } from "../../../blocks/defaultBlocks.js";
import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor";
import {
BlockSchema,
FileBlockConfig,
InlineContentSchema,
StyleSchema,
} from "../../../schema/index.js";
import { getNearestBlockPos } from "../../getBlockInfoFromPos.js";
import { acceptedMIMETypes } from "./acceptedMIMETypes.js";
function checkFileExtensionsMatch(
fileExtension1: string,
fileExtension2: string,
) {
if (!fileExtension1.startsWith(".") || !fileExtension2.startsWith(".")) {
throw new Error(`The strings provided are not valid file extensions.`);
}
return fileExtension1 === fileExtension2;
}
function checkMIMETypesMatch(mimeType1: string, mimeType2: string) {
const types1 = mimeType1.split("/");
const types2 = mimeType2.split("/");
if (types1.length !== 2) {
throw new Error(`The string ${mimeType1} is not a valid MIME type.`);
}
if (types2.length !== 2) {
throw new Error(`The string ${mimeType2} is not a valid MIME type.`);
}
if (types1[1] === "*" || types2[1] === "*") {
return types1[0] === types2[0];
}
if (types1[0] === "*" || types2[0] === "*") {
return types1[1] === types2[1];
}
return types1[0] === types2[0] && types1[1] === types2[1];
}
function insertOrUpdateBlock<
BSchema extends BlockSchema,
I extends InlineContentSchema,
S extends StyleSchema,
>(
editor: BlockNoteEditor<BSchema, I, S>,
referenceBlock: Block<BSchema, I, S>,
newBlock: PartialBlock<BSchema, I, S>,
) {
let insertedBlockId: string | undefined;
if (
Array.isArray(referenceBlock.content) &&
referenceBlock.content.length === 0
) {
insertedBlockId = editor.updateBlock(referenceBlock, newBlock).id;
} else {
insertedBlockId = editor.insertBlocks(
[newBlock],
referenceBlock,
"after",
)[0].id;
}
return insertedBlockId;
}
export async function handleFileInsertion<
BSchema extends BlockSchema,
I extends InlineContentSchema,
S extends StyleSchema,
>(event: DragEvent | ClipboardEvent, editor: BlockNoteEditor<BSchema, I, S>) {
if (!editor.uploadFile) {
// eslint-disable-next-line no-console
console.warn(
"Attempted ot insert file, but uploadFile is not set in the BlockNote editor options",
);
return;
}
const dataTransfer =
"dataTransfer" in event ? event.dataTransfer : event.clipboardData;
if (dataTransfer === null) {
return;
}
let format: (typeof acceptedMIMETypes)[number] | null = null;
for (const mimeType of acceptedMIMETypes) {
if (dataTransfer.types.includes(mimeType)) {
format = mimeType;
break;
}
}
if (format !== "Files") {
return;
}
const items = dataTransfer.items;
if (!items) {
return;
}
event.preventDefault();
const fileBlockConfigs = Object.values(editor.schema.blockSchema).filter(
(blockConfig) => blockConfig.isFileBlock,
) as FileBlockConfig[];
for (let i = 0; i < items.length; i++) {
// Gets file block corresponding to MIME type.
let fileBlockType = "file";
for (const fileBlockConfig of fileBlockConfigs) {
for (const mimeType of fileBlockConfig.fileBlockAccept || []) {
const isFileExtension = mimeType.startsWith(".");
const file = items[i].getAsFile();
if (file) {
if (
(!isFileExtension &&
file.type &&
checkMIMETypesMatch(items[i].type, mimeType)) ||
(isFileExtension &&
checkFileExtensionsMatch(
"." + file.name.split(".").pop(),
mimeType,
))
) {
fileBlockType = fileBlockConfig.type;
break;
}
}
}
}
const file = items[i].getAsFile();
if (file) {
const fileBlock = {
type: fileBlockType,
props: {
name: file.name,
},
} as PartialBlock<BSchema, I, S>;
let insertedBlockId: string | undefined = undefined;
if (event.type === "paste") {
const currentBlock = editor.getTextCursorPosition().block;
insertedBlockId = insertOrUpdateBlock(editor, currentBlock, fileBlock);
} else if (event.type === "drop") {
const coords = {
left: (event as DragEvent).clientX,
top: (event as DragEvent).clientY,
};
const pos = editor.prosemirrorView?.posAtCoords(coords);
if (!pos) {
return;
}
insertedBlockId = editor.transact((tr) => {
const posInfo = getNearestBlockPos(tr.doc, pos.pos);
return insertOrUpdateBlock(
editor,
editor.getBlock(posInfo.node.attrs.id)!,
fileBlock,
);
});
} else {
return;
}
const updateData = await editor.uploadFile(file, insertedBlockId);
const updatedFileBlock =
typeof updateData === "string"
? ({
props: {
url: updateData,
},
} as PartialBlock<BSchema, I, S>)
: { ...updateData };
editor.updateBlock(insertedBlockId, updatedFileBlock);
}
}
}