-
-
Notifications
You must be signed in to change notification settings - Fork 525
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
feat: markdown pasting & custom paste handlers #1490
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 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 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 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 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 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 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 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 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 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 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 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 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?