@blocknote/core
Version:
A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.
153 lines (132 loc) • 3.69 kB
text/typescript
import { Extension } from "@tiptap/core";
import { Plugin } from "prosemirror-state";
import type {
BlockNoteEditor,
BlockNoteEditorOptions,
} from "../../../editor/BlockNoteEditor";
import { isMarkdown } from "../../parsers/markdown/detectMarkdown.js";
import {
BlockSchema,
InlineContentSchema,
StyleSchema,
} from "../../../schema/index.js";
import { acceptedMIMETypes } from "./acceptedMIMETypes.js";
import { handleFileInsertion } from "./handleFileInsertion.js";
import { handleVSCodePaste } from "./handleVSCodePaste.js";
function defaultPasteHandler({
event,
editor,
prioritizeMarkdownOverHTML,
plainTextAsMarkdown,
}: {
event: ClipboardEvent;
editor: BlockNoteEditor<any, any, any>;
prioritizeMarkdownOverHTML: boolean;
plainTextAsMarkdown: boolean;
}) {
// Special case for code blocks, as they do not support any rich text
// formatting, so we force pasting plain text.
const isInCodeBlock = editor.transact(
(tr) =>
tr.selection.$from.parent.type.spec.code &&
tr.selection.$to.parent.type.spec.code,
);
if (isInCodeBlock) {
const data = event.clipboardData?.getData("text/plain");
if (data) {
editor.pasteText(data);
return true;
}
}
let format: (typeof acceptedMIMETypes)[number] | undefined;
for (const mimeType of acceptedMIMETypes) {
if (event.clipboardData!.types.includes(mimeType)) {
format = mimeType;
break;
}
}
if (!format) {
return true;
}
if (format === "vscode-editor-data") {
handleVSCodePaste(event, editor.prosemirrorView!);
return true;
}
if (format === "Files") {
handleFileInsertion(event, editor);
return true;
}
const data = event.clipboardData!.getData(format);
if (format === "blocknote/html") {
// Is blocknote/html, so no need to convert it
editor.pasteHTML(data, true);
return true;
}
if (format === "text/markdown") {
editor.pasteMarkdown(data);
return true;
}
if (prioritizeMarkdownOverHTML) {
// Use plain text instead of HTML if it looks like Markdown
const plainText = event.clipboardData!.getData("text/plain");
if (isMarkdown(plainText)) {
editor.pasteMarkdown(plainText);
return true;
}
}
if (format === "text/html") {
editor.pasteHTML(data);
return true;
}
if (plainTextAsMarkdown) {
editor.pasteMarkdown(data);
return true;
}
editor.pasteText(data);
return true;
}
export const createPasteFromClipboardExtension = <
BSchema extends BlockSchema,
I extends InlineContentSchema,
S extends StyleSchema,
>(
editor: BlockNoteEditor<BSchema, I, S>,
pasteHandler: Exclude<
BlockNoteEditorOptions<any, any, any>["pasteHandler"],
undefined
>,
) =>
Extension.create({
name: "pasteFromClipboard",
addProseMirrorPlugins() {
return [
new Plugin({
props: {
handleDOMEvents: {
paste(_view, event) {
event.preventDefault();
if (!editor.isEditable) {
return;
}
return pasteHandler({
event,
editor,
defaultPasteHandler: ({
prioritizeMarkdownOverHTML = true,
plainTextAsMarkdown = true,
} = {}) => {
return defaultPasteHandler({
event,
editor,
prioritizeMarkdownOverHTML,
plainTextAsMarkdown,
});
},
});
},
},
},
}),
];
},
});