|
1 |
| -import { Plugin, PluginKey } from "prosemirror-state"; |
| 1 | +import { Plugin, PluginKey, TextSelection } from "prosemirror-state"; |
2 | 2 |
|
3 | 3 | const PLUGIN_KEY = new PluginKey("non-editable-block");
|
4 |
| -// Prevent typing for blocks without inline content, as this would otherwise |
5 |
| -// convert them into paragraph blocks. |
| 4 | +// By default, typing with a node selection active will cause ProseMirror to |
| 5 | +// replace the node with one that contains editable content. This plugin blocks |
| 6 | +// this behaviour without also blocking things like keyboard shortcuts: |
| 7 | +// |
| 8 | +// - Lets through key presses that do not include alphanumeric characters. This |
| 9 | +// includes things like backspace/delete/home/end/etc. |
| 10 | +// - Lets through any key presses that include ctrl/meta keys. These will be |
| 11 | +// shortcuts of some kind like ctrl+C/mod+C. |
| 12 | +// - Special case for Enter key which creates a new paragraph block below and |
| 13 | +// sets the selection to it. This is just to bring the UX closer to Notion |
| 14 | +// |
| 15 | +// While a more elegant solution would probably process transactions instead of |
| 16 | +// keystrokes, this brings us most of the way to Notion's UX without much added |
| 17 | +// complexity. |
6 | 18 | export const NonEditableBlockPlugin = () => {
|
7 | 19 | return new Plugin({
|
8 | 20 | key: PLUGIN_KEY,
|
9 | 21 | props: {
|
10 | 22 | handleKeyDown: (view, event) => {
|
11 | 23 | // Checks for node selection
|
12 | 24 | if ("node" in view.state.selection) {
|
13 |
| - // Checks if key input will insert a character - we want to block this |
14 |
| - // as it will convert the block into a paragraph. |
15 |
| - if ( |
16 |
| - event.key.length === 1 && |
17 |
| - !event.ctrlKey && |
18 |
| - !event.altKey && |
19 |
| - !event.metaKey && |
20 |
| - !event.shiftKey |
21 |
| - ) { |
| 25 | + // Checks if key press uses ctrl/meta modifier |
| 26 | + if (event.ctrlKey || event.metaKey) { |
| 27 | + return false; |
| 28 | + } |
| 29 | + // Checks if key press is alphanumeric |
| 30 | + if (event.key.length === 1) { |
22 | 31 | event.preventDefault();
|
| 32 | + |
| 33 | + return true; |
| 34 | + } |
| 35 | + // Checks if key press is Enter |
| 36 | + if (event.key === "Enter") { |
| 37 | + const tr = view.state.tr; |
| 38 | + view.dispatch( |
| 39 | + tr |
| 40 | + .insert( |
| 41 | + view.state.tr.selection.$to.after(), |
| 42 | + view.state.schema.nodes["paragraph"].create() |
| 43 | + ) |
| 44 | + .setSelection( |
| 45 | + new TextSelection( |
| 46 | + tr.doc.resolve(view.state.tr.selection.$to.after() + 1) |
| 47 | + ) |
| 48 | + ) |
| 49 | + ); |
| 50 | + |
| 51 | + return true; |
23 | 52 | }
|
24 | 53 | }
|
| 54 | + |
| 55 | + return false; |
25 | 56 | },
|
26 | 57 | },
|
27 | 58 | });
|
|
0 commit comments