diff --git a/docs/pages/docs/advanced/paste-handling.mdx b/docs/pages/docs/advanced/paste-handling.mdx new file mode 100644 index 0000000000..de3293a02f --- /dev/null +++ b/docs/pages/docs/advanced/paste-handling.mdx @@ -0,0 +1,74 @@ +--- +title: Paste Handling +description: This section explains how to handle paste events in BlockNote. +imageTitle: Paste Handling +--- + +import { Example } from "@/components/example"; + +# Paste Handling + +BlockNote, by default, attempts to paste content in the following order: + +- VS Code compatible content +- Files +- BlockNote HTML +- Markdown +- HTML +- Plain text + +> In certain cases, BlockNote will attempt to detect markdown in the clipboard and paste that into the editor as rich text. + +You can change the default paste behavior by providing a custom paste handler, which will give you full control over how pasted content is inserted into the editor. + +## `pasteHandler` option + +The `pasteHandler` option is a function that receives the following arguments: + +```ts +type PasteHandler = (context: { + event: ClipboardEvent; + editor: BlockNoteEditor; + defaultPasteHandler: (context?: { + prioritizeMarkdownOverHTML?: boolean; + plainTextAsMarkdown?: boolean; + }) => boolean; +}) => boolean; +``` + +- `event`: The paste event. +- `editor`: The current editor instance. +- `defaultPasteHandler`: The default paste handler. If you only need to customize the paste behavior a little bit, you can fall back on the default paste handler. + +The `defaultPasteHandler` function can be called with the following options: + +- `prioritizeMarkdownOverHTML`: Whether to prioritize Markdown content in `text/plain` over `text/html` when pasting from the clipboard. +- `plainTextAsMarkdown`: Whether to interpret plain text as markdown and paste that as rich text or to paste the text directly into the editor. + + +## Custom Paste Handler + +You can also provide your own paste handler by providing a function to the `pasteHandler` option. + +In this example, we handle the paste event if the clipboard data contains `text/my-custom-format`. If we don't handle the paste event, we call the default paste handler to do the default behavior. + +```ts +const editor = new BlockNoteEditor({ + pasteHandler: ({ event, editor, defaultPasteHandler }) => { + if (event.clipboardData?.types.includes("text/my-custom-format")) { + // You can do any custom logic here, for example you could transform the clipboard data before pasting it + const markdown = customToMarkdown(event.clipboardData.getData("text/my-custom-format")); + + // The editor is able paste markdown (`pasteMarkdown`), HTML (`pasteHTML`), or plain text (`pasteText`) + editor.pasteMarkdown(markdown); + // We handled the paste event, so return true, returning false will cancel the paste event + return true; + } + + // If we didn't handle the paste event, call the default paste handler to do the default behavior + return defaultPasteHandler(); + }, +}); +``` + +See an example of this in the [Custom Paste Handler](/examples/basic/custom-paste-handler) example. \ No newline at end of file diff --git a/docs/pages/docs/editor-basics/setup.mdx b/docs/pages/docs/editor-basics/setup.mdx index 5c76f60ea4..fbb8795f89 100644 --- a/docs/pages/docs/editor-basics/setup.mdx +++ b/docs/pages/docs/editor-basics/setup.mdx @@ -33,6 +33,13 @@ type BlockNoteEditorOptions = { class?: string; }) => Plugin; initialContent?: PartialBlock[]; + pasteHandler?: (context: { + event: ClipboardEvent; + editor: BlockNoteEditor; + defaultPasteHandler: (context: { + pasteBehavior?: "prefer-markdown" | "prefer-html"; + }) => boolean | undefined; + }) => boolean | undefined; resolveFileUrl: (url: string) => Promise schema?: BlockNoteSchema; setIdAttribute?: boolean; @@ -66,6 +73,8 @@ The hook takes two optional parameters: `initialContent:` The content that should be in the editor when it's created, represented as an array of [Partial Blocks](/docs/manipulating-blocks#partial-blocks). +`pasteHandler`: A function that can be used to override the default paste behavior. See [Paste Handling](/docs/advanced/paste-handling) for more. + `resolveFileUrl:` Function to resolve file URLs for display/download. Useful for creating authenticated URLs or implementing custom protocols. `resolveUsers`: Function to resolve user information for comments. See [Comments](/docs/collaboration/comments) for more. diff --git a/examples/01-basic/13-custom-paste-handler/.bnexample.json b/examples/01-basic/13-custom-paste-handler/.bnexample.json new file mode 100644 index 0000000000..5778b19ace --- /dev/null +++ b/examples/01-basic/13-custom-paste-handler/.bnexample.json @@ -0,0 +1,6 @@ +{ + "playground": true, + "docs": true, + "author": "nperez0111", + "tags": ["Basic"] +} diff --git a/examples/01-basic/13-custom-paste-handler/App.tsx b/examples/01-basic/13-custom-paste-handler/App.tsx new file mode 100644 index 0000000000..7adafbc635 --- /dev/null +++ b/examples/01-basic/13-custom-paste-handler/App.tsx @@ -0,0 +1,105 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; + +import "./styles.css"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: [ + { + styles: {}, + type: "text", + text: "Paste some text here", + }, + ], + }, + ], + pasteHandler: ({ event, editor, defaultPasteHandler }) => { + if (event.clipboardData?.types.includes("text/plain")) { + editor.pasteMarkdown( + event.clipboardData.getData("text/plain") + + " - inserted by the custom paste handler" + ); + return true; + } + return defaultPasteHandler(); + }, + }); + + // Renders the editor instance using a React component. + return ( +
+ +
+ + + + +
+
+ ); +} diff --git a/examples/01-basic/13-custom-paste-handler/README.md b/examples/01-basic/13-custom-paste-handler/README.md new file mode 100644 index 0000000000..189248704e --- /dev/null +++ b/examples/01-basic/13-custom-paste-handler/README.md @@ -0,0 +1,9 @@ +# Custom Paste Handler + +In this example, we change the default paste handler to append some text to the pasted content when the content is plain text. + +**Try it out:** Use the buttons to copy some content to the clipboard and paste it in the editor to trigger our custom paste handler. + +**Relevant Docs:** + +- [Paste Handling](/docs/advanced/paste-handling) diff --git a/examples/01-basic/13-custom-paste-handler/index.html b/examples/01-basic/13-custom-paste-handler/index.html new file mode 100644 index 0000000000..9b469194a1 --- /dev/null +++ b/examples/01-basic/13-custom-paste-handler/index.html @@ -0,0 +1,14 @@ + + + + + + Custom Paste Handler + + +
+ + + diff --git a/examples/01-basic/13-custom-paste-handler/main.tsx b/examples/01-basic/13-custom-paste-handler/main.tsx new file mode 100644 index 0000000000..f88b490fbd --- /dev/null +++ b/examples/01-basic/13-custom-paste-handler/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + +); diff --git a/examples/01-basic/13-custom-paste-handler/package.json b/examples/01-basic/13-custom-paste-handler/package.json new file mode 100644 index 0000000000..4c06198cde --- /dev/null +++ b/examples/01-basic/13-custom-paste-handler/package.json @@ -0,0 +1,37 @@ +{ + "name": "@blocknote/example-custom-paste-handler", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint . --max-warnings 0" + }, + "dependencies": { + "@blocknote/core": "latest", + "@blocknote/react": "latest", + "@blocknote/ariakit": "latest", + "@blocknote/mantine": "latest", + "@blocknote/shadcn": "latest", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.0.25", + "@types/react-dom": "^18.0.9", + "@vitejs/plugin-react": "^4.3.1", + "eslint": "^8.10.0", + "vite": "^5.3.4" + }, + "eslintConfig": { + "extends": [ + "../../../.eslintrc.js" + ] + }, + "eslintIgnore": [ + "dist" + ] +} \ No newline at end of file diff --git a/examples/01-basic/13-custom-paste-handler/styles.css b/examples/01-basic/13-custom-paste-handler/styles.css new file mode 100644 index 0000000000..eaade15302 --- /dev/null +++ b/examples/01-basic/13-custom-paste-handler/styles.css @@ -0,0 +1,15 @@ +.edit-buttons { + display: flex; + justify-content: space-between; + margin-top: 8px; +} + +.edit-button { + border: 1px solid gray; + border-radius: 4px; + padding-inline: 4px; +} + +.edit-button:hover { + border: 1px solid lightgrey; +} \ No newline at end of file diff --git a/examples/01-basic/13-custom-paste-handler/tsconfig.json b/examples/01-basic/13-custom-paste-handler/tsconfig.json new file mode 100644 index 0000000000..dbe3e6f62d --- /dev/null +++ b/examples/01-basic/13-custom-paste-handler/tsconfig.json @@ -0,0 +1,36 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": [ + "DOM", + "DOM.Iterable", + "ESNext" + ], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": [ + "." + ], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} \ No newline at end of file diff --git a/examples/01-basic/13-custom-paste-handler/vite.config.ts b/examples/01-basic/13-custom-paste-handler/vite.config.ts new file mode 100644 index 0000000000..f62ab20bc2 --- /dev/null +++ b/examples/01-basic/13-custom-paste-handler/vite.config.ts @@ -0,0 +1,32 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite"; +// import eslintPlugin from "vite-plugin-eslint"; +// https://vitejs.dev/config/ +export default defineConfig((conf) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/" + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/" + ), + } as any), + }, +})); diff --git a/packages/core/src/api/clipboard/fromClipboard/acceptedMIMETypes.ts b/packages/core/src/api/clipboard/fromClipboard/acceptedMIMETypes.ts index eabefb3c49..c79400c78a 100644 --- a/packages/core/src/api/clipboard/fromClipboard/acceptedMIMETypes.ts +++ b/packages/core/src/api/clipboard/fromClipboard/acceptedMIMETypes.ts @@ -1,6 +1,7 @@ export const acceptedMIMETypes = [ "vscode-editor-data", "blocknote/html", + "text/markdown", "text/html", "text/plain", "Files", diff --git a/packages/core/src/api/clipboard/fromClipboard/pasteExtension.ts b/packages/core/src/api/clipboard/fromClipboard/pasteExtension.ts index 350cf2dad8..ecc17120ee 100644 --- a/packages/core/src/api/clipboard/fromClipboard/pasteExtension.ts +++ b/packages/core/src/api/clipboard/fromClipboard/pasteExtension.ts @@ -1,23 +1,100 @@ import { Extension } from "@tiptap/core"; import { Plugin } from "prosemirror-state"; -import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor"; +import type { + BlockNoteEditor, + BlockNoteEditorOptions, +} from "../../../editor/BlockNoteEditor"; import { BlockSchema, InlineContentSchema, StyleSchema, } from "../../../schema/index.js"; -import { nestedListsToBlockNoteStructure } from "../../parsers/html/util/nestedLists.js"; import { acceptedMIMETypes } from "./acceptedMIMETypes.js"; import { handleFileInsertion } from "./handleFileInsertion.js"; import { handleVSCodePaste } from "./handleVSCodePaste.js"; +import { isMarkdown } from "../../parsers/markdown/detectMarkdown.js"; + +function defaultPasteHandler({ + event, + editor, + prioritizeMarkdownOverHTML, + plainTextAsMarkdown, +}: { + event: ClipboardEvent; + editor: BlockNoteEditor; + prioritizeMarkdownOverHTML: boolean; + plainTextAsMarkdown: boolean; +}) { + 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 + editor: BlockNoteEditor, + pasteHandler: Exclude< + BlockNoteEditorOptions["pasteHandler"], + undefined + > ) => Extension.create({ name: "pasteFromClipboard", @@ -26,51 +103,28 @@ export const createPasteFromClipboardExtension = < new Plugin({ props: { handleDOMEvents: { - paste(view, event) { + paste(_view, event) { event.preventDefault(); if (!editor.isEditable) { return; } - 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, view); - return true; - } - - if (format === "Files") { - handleFileInsertion(event, editor); - return true; - } - - let data = event.clipboardData!.getData(format); - - if (format === "blocknote/html") { - view.pasteHTML(data); - return true; - } - - if (format === "text/html") { - const htmlNode = nestedListsToBlockNoteStructure(data.trim()); - data = htmlNode.innerHTML; - view.pasteHTML(data); - return true; - } - - view.pasteText(data); - - return true; + return pasteHandler({ + event, + editor, + defaultPasteHandler: ({ + prioritizeMarkdownOverHTML = true, + plainTextAsMarkdown = true, + } = {}) => { + return defaultPasteHandler({ + event, + editor, + prioritizeMarkdownOverHTML, + plainTextAsMarkdown, + }); + }, + }); }, }, }, diff --git a/packages/core/src/api/parsers/markdown/detectMarkdown.ts b/packages/core/src/api/parsers/markdown/detectMarkdown.ts new file mode 100644 index 0000000000..92734face4 --- /dev/null +++ b/packages/core/src/api/parsers/markdown/detectMarkdown.ts @@ -0,0 +1,60 @@ +// Headings H1-H6. +const h1 = /(^|\n) {0,3}#{1,6} {1,8}[^\n]{1,64}\r?\n\r?\n\s{0,32}\S/; + +// Bold, italic, underline, strikethrough, highlight. +const bold = /(?:\s|^)(_|__|\*|\*\*|~~|==|\+\+)(?!\s).{1,64}(?[^\n]{1,333}\n){1,999}($|(\r?\n))/; + +// Table Header +const tableHeader = /^\s*\|(.+\|)+\s*$/m; + +// Table Divider +const tableDivider = /^\s*\|(\s*[-:]+[-:]\s*\|)+\s*$/m; + +// Table Row +const tableRow = /^\s*\|(.+\|)+\s*$/m; + +/** + * Returns `true` if the source text might be a markdown document. + * + * @param src Source text to analyze. + */ +export const isMarkdown = (src: string): boolean => + h1.test(src) || + bold.test(src) || + link.test(src) || + code.test(src) || + ul.test(src) || + ol.test(src) || + hr.test(src) || + fences.test(src) || + title.test(src) || + blockquote.test(src) || + tableHeader.test(src) || + tableDivider.test(src) || + tableRow.test(src); diff --git a/packages/core/src/api/parsers/markdown/parseMarkdown.ts b/packages/core/src/api/parsers/markdown/parseMarkdown.ts index 2d6b81873e..1c348d8ffe 100644 --- a/packages/core/src/api/parsers/markdown/parseMarkdown.ts +++ b/packages/core/src/api/parsers/markdown/parseMarkdown.ts @@ -48,17 +48,7 @@ function code(state: any, node: any) { return result; } -export async function markdownToBlocks< - BSchema extends BlockSchema, - I extends InlineContentSchema, - S extends StyleSchema ->( - markdown: string, - blockSchema: BSchema, - icSchema: I, - styleSchema: S, - pmSchema: Schema -): Promise[]> { +export async function markdownToHTML(markdown: string): Promise { const deps = await initializeESMDependencies(); const htmlString = deps.unified @@ -74,11 +64,21 @@ export async function markdownToBlocks< .use(deps.rehypeStringify.default) .processSync(markdown); - return HTMLToBlocks( - htmlString.value as string, - blockSchema, - icSchema, - styleSchema, - pmSchema - ); + return htmlString.value as string; +} + +export async function markdownToBlocks< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + markdown: string, + blockSchema: BSchema, + icSchema: I, + styleSchema: S, + pmSchema: Schema +): Promise[]> { + const htmlString = await markdownToHTML(markdown); + + return HTMLToBlocks(htmlString, blockSchema, icSchema, styleSchema, pmSchema); } diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index c0e9185629..b1caec71b0 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -43,7 +43,10 @@ import { import { createExternalHTMLExporter } from "../api/exporters/html/externalHTMLExporter.js"; import { blocksToMarkdown } from "../api/exporters/markdown/markdownExporter.js"; import { HTMLToBlocks } from "../api/parsers/html/parseHTML.js"; -import { markdownToBlocks } from "../api/parsers/markdown/parseMarkdown.js"; +import { + markdownToBlocks, + markdownToHTML, +} from "../api/parsers/markdown/parseMarkdown.js"; import { Block, DefaultBlockSchema, @@ -102,6 +105,7 @@ import type { ThreadStore, User } from "../comments/index.js"; import "../style.css"; import { EventEmitter } from "../util/EventEmitter.js"; import { CodeBlockOptions } from "../blocks/CodeBlockContent/CodeBlockContent.js"; +import { nestedListsToBlockNoteStructure } from "../api/parsers/html/util/nestedLists.js"; export type BlockNoteExtensionFactory = ( editor: BlockNoteEditor @@ -218,6 +222,39 @@ export type BlockNoteEditorOptions< string | undefined >; + /** + * Custom paste handler that can be used to override the default paste behavior. + * @returns The function should return `true` if the paste event was handled, otherwise it should return `false` if it should be canceled or `undefined` if it should be handled by another handler. + * + * @example + * ```ts + * pasteHandler: ({ defaultPasteHandler }) => { + * return defaultPasteHandler({ pasteBehavior: "prefer-html" }); + * } + * ``` + */ + pasteHandler?: (context: { + event: ClipboardEvent; + editor: BlockNoteEditor; + /** + * The default paste handler + * @param context The context object + * @returns Whether the paste event was handled or not + */ + defaultPasteHandler: (context?: { + /** + * Whether to prioritize Markdown content in `text/plain` over `text/html` when pasting from the clipboard. + * @default true + */ + prioritizeMarkdownOverHTML?: boolean; + /** + * Whether to parse `text/plain` content from the clipboard as Markdown content. + * @default true + */ + plainTextAsMarkdown?: boolean; + }) => boolean | undefined; + }) => boolean | undefined; + /** * Resolve a URL of a file block to one that can be displayed or downloaded. This can be used for creating authenticated URL or * implementing custom protocols / schemes @@ -545,6 +582,7 @@ export class BlockNoteEditor< tabBehavior: newOptions.tabBehavior, sideMenuDetection: newOptions.sideMenuDetection || "viewport", comments: newOptions.comments, + pasteHandler: newOptions.pasteHandler, }); // add extensions from _tiptapOptions @@ -1445,4 +1483,41 @@ export class BlockNoteEditor< public setForceSelectionVisible(forceSelectionVisible: boolean) { this.showSelectionPlugin.setEnabled(forceSelectionVisible); } + + /** + * This will convert HTML into a format that is compatible with BlockNote. + */ + private convertHtmlToBlockNoteHtml(html: string) { + const htmlNode = nestedListsToBlockNoteStructure(html.trim()); + return htmlNode.innerHTML; + } + + /** + * Paste HTML into the editor. Defaults to converting HTML to BlockNote HTML. + * @param html The HTML to paste. + * @param raw Whether to paste the HTML as is, or to convert it to BlockNote HTML. + */ + public pasteHTML(html: string, raw = false) { + let htmlToPaste = html; + if (!raw) { + htmlToPaste = this.convertHtmlToBlockNoteHtml(html); + } + this.prosemirrorView?.pasteHTML(htmlToPaste); + } + + /** + * Paste text into the editor. Defaults to interpreting text as markdown. + * @param text The text to paste. + */ + public pasteText(text: string) { + return this.prosemirrorView?.pasteText(text); + } + + /** + * Paste markdown into the editor. + * @param markdown The markdown to paste. + */ + public async pasteMarkdown(markdown: string) { + return this.pasteHTML(await markdownToHTML(markdown)); + } } diff --git a/packages/core/src/editor/BlockNoteExtensions.ts b/packages/core/src/editor/BlockNoteExtensions.ts index a5233f11f6..932ca91287 100644 --- a/packages/core/src/editor/BlockNoteExtensions.ts +++ b/packages/core/src/editor/BlockNoteExtensions.ts @@ -44,7 +44,11 @@ import { StyleSchema, StyleSpecs, } from "../schema/index.js"; -import type { BlockNoteEditor, BlockNoteExtension } from "./BlockNoteEditor.js"; +import type { + BlockNoteEditor, + BlockNoteEditorOptions, + BlockNoteExtension, +} from "./BlockNoteEditor.js"; type ExtensionOptions< BSchema extends BlockSchema, @@ -82,6 +86,7 @@ type ExtensionOptions< comments?: { threadStore: ThreadStore; }; + pasteHandler: BlockNoteEditorOptions["pasteHandler"]; }; /** @@ -258,7 +263,16 @@ const getTipTapExtensions = < ]; }), createCopyToClipboardExtension(opts.editor), - createPasteFromClipboardExtension(opts.editor), + createPasteFromClipboardExtension( + opts.editor, + opts.pasteHandler || + ((context: { + defaultPasteHandler: (context?: { + prioritizeMarkdownOverHTML?: boolean; + plainTextAsMarkdown?: boolean; + }) => boolean | undefined; + }) => context.defaultPasteHandler()) + ), createDropFileExtension(opts.editor), // This needs to be at the bottom of this list, because Key events (such as enter, when selecting a /command), diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx index 5d1fc918c2..a793470da8 100644 --- a/playground/src/examples.gen.tsx +++ b/playground/src/examples.gen.tsx @@ -232,6 +232,24 @@ "slug": "basic" } }, + { + "projectSlug": "custom-paste-handler", + "fullSlug": "basic/custom-paste-handler", + "pathFromRoot": "examples/01-basic/13-custom-paste-handler", + "config": { + "playground": true, + "docs": true, + "author": "nperez0111", + "tags": [ + "Basic" + ] + }, + "title": "Custom Paste Handler", + "group": { + "pathFromRoot": "examples/01-basic", + "slug": "basic" + } + }, { "projectSlug": "testing", "fullSlug": "basic/testing",