- 
          
- 
                Notifications
    You must be signed in to change notification settings 
- Fork 626
feat: markdown pasting & custom paste handlers #1490
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
          
     Merged
      
      
    
  
     Merged
                    Changes from all commits
      Commits
    
    
            Show all changes
          
          
            21 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      1ced711
              
                feat: an attempt at parsing markdown on paste
              
              
                nperez0111 f60d856
              
                chore: comment
              
              
                nperez0111 29162fa
              
                feat: paste as HTML
              
              
                nperez0111 7f61926
              
                fix deploy
              
              
                YousefED c060eee
              
                feat: check if pasting looks like markdown
              
              
                nperez0111 1681c62
              
                refactor: better naming for detecting markdown
              
              
                nperez0111 1f3ad10
              
                feat: implement a pastHandler API
              
              
                nperez0111 aee8dcc
              
                docs: add docs for new option
              
              
                nperez0111 2045027
              
                docs: forgot one option
              
              
                nperez0111 52ce922
              
                refactor: no need to expose pasteHandler
              
              
                nperez0111 f20e37d
              
                refactor: pasteText & pasteHTML APIs
              
              
                nperez0111 5ce1571
              
                docs: add docs on paste handling
              
              
                nperez0111 913916d
              
                refactor: clean up logic
              
              
                nperez0111 f3822e5
              
                refactor: split out pasteMarkdown method
              
              
                nperez0111 c892e7f
              
                docs: update link
              
              
                nperez0111 af0c5ff
              
                refactor: separate autodetectMarkdown from plaintextAsMarkdown
              
              
                nperez0111 0670c99
              
                chore: update example text
              
              
                nperez0111 c055b4c
              
                docs: update readme
              
              
                nperez0111 83bd820
              
                chore: add period
              
              
                nperez0111 b232332
              
                refactor: rename autodetectMarkdown -> prioritizeMarkdownOverHTML
              
              
                nperez0111 7ed58cb
              
                Merge branch 'main' into markdown-paste
              
              
                nperez0111 File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -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; | ||
|         
                  nperez0111 marked this conversation as resolved.
              Show resolved
            Hide resolved | ||
| } | ||
|  | ||
| // 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. | ||
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "playground": true, | ||
| "docs": true, | ||
| "author": "nperez0111", | ||
| "tags": ["Basic"] | ||
| } | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -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 ( | ||
| <div> | ||
| <BlockNoteView editor={editor} /> | ||
| <div className={"edit-buttons"}> | ||
| <button | ||
| className={"edit-button"} | ||
| onClick={async () => { | ||
| try { | ||
| await navigator.clipboard.writeText( | ||
| "**This is markdown in the plain text format**" | ||
| ); | ||
| } catch (error) { | ||
| window.alert("Failed to copy plain text with markdown content"); | ||
| } | ||
| }}> | ||
| Copy sample markdown to clipboard (text/plain) | ||
| </button> | ||
| <button | ||
| className={"edit-button"} | ||
| onClick={async () => { | ||
| try { | ||
| await navigator.clipboard.write([ | ||
| new ClipboardItem({ | ||
| "text/html": "<p><strong>HTML</strong></p>", | ||
| }), | ||
| ]); | ||
| } catch (error) { | ||
| window.alert("Failed to copy HTML content"); | ||
| } | ||
| }}> | ||
| Copy sample HTML to clipboard (text/html) | ||
| </button> | ||
| <button | ||
| className={"edit-button"} | ||
| onClick={async () => { | ||
| try { | ||
| await navigator.clipboard.writeText( | ||
| "This is plain text in the plain text format" | ||
| ); | ||
| } catch (error) { | ||
| window.alert("Failed to copy plain text"); | ||
| } | ||
| }}> | ||
| Copy sample plain text to clipboard (text/plain) | ||
| </button> | ||
| <button | ||
| className={"edit-button"} | ||
| onClick={async () => { | ||
| try { | ||
| await navigator.clipboard.write([ | ||
| new ClipboardItem({ | ||
| "text/plain": "Plain text", | ||
| }), | ||
| new ClipboardItem({ | ||
| "text/html": "<p><strong>HTML</strong></p>", | ||
| }), | ||
| new ClipboardItem({ | ||
| "text/markdown": "**Markdown**", | ||
| }), | ||
| ]); | ||
| } catch (error) { | ||
| window.alert("Failed to copy multiple formats"); | ||
| There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm getting this error when I click this button :/ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not supported by most browsers, but works in Safari. I marked as such | ||
| } | ||
| }}> | ||
| Copy sample markdown, HTML, and plain text to clipboard (Safari only) | ||
| </button> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -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) | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <html lang="en"> | ||
| <head> | ||
| <script> | ||
| <!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY --> | ||
| </script> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>Custom Paste Handler</title> | ||
| </head> | ||
| <body> | ||
| <div id="root"></div> | ||
| <script type="module" src="./main.tsx"></script> | ||
| </body> | ||
| </html> | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -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( | ||
| <React.StrictMode> | ||
| <App /> | ||
| </React.StrictMode> | ||
| ); | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -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" | ||
| ] | ||
| } | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -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; | ||
| } | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -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/" | ||
| } | ||
| ] | ||
| } | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -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), | ||
| }, | ||
| })); | 
        
          
          
            1 change: 1 addition & 0 deletions
          
          1 
        
  packages/core/src/api/clipboard/fromClipboard/acceptedMIMETypes.ts
  
  
      
      
   
        
      
      
    
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
              
      
      Oops, something went wrong.
        
    
  
      
      Oops, something went wrong.
        
    
  
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needs to be added to sidebar
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is in the sidebar?