@blocknote/core 0.19.0 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/blocknote.js +2549 -2497
- package/dist/blocknote.js.map +1 -1
- package/dist/blocknote.umd.cjs +6 -6
- package/dist/blocknote.umd.cjs.map +1 -1
- package/dist/src/api/blockManipulation/commands/moveBlock/moveBlock.test.js +5 -1
- package/dist/src/api/blockManipulation/commands/moveBlock/moveBlock.test.js.map +1 -1
- package/dist/src/api/blockManipulation/commands/removeBlocks/removeBlocks.js +2 -40
- package/dist/src/api/blockManipulation/commands/removeBlocks/removeBlocks.js.map +1 -1
- package/dist/src/api/blockManipulation/commands/removeBlocks/removeBlocks.test.js +4 -0
- package/dist/src/api/blockManipulation/commands/removeBlocks/removeBlocks.test.js.map +1 -1
- package/dist/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.js +51 -9
- package/dist/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.js.map +1 -1
- package/dist/src/api/blockManipulation/commands/splitBlock/splitBlock.js +2 -2
- package/dist/src/api/blockManipulation/commands/splitBlock/splitBlock.js.map +1 -1
- package/dist/src/api/clipboard/fromClipboard/acceptedMIMETypes.js +1 -1
- package/dist/src/api/clipboard/fromClipboard/acceptedMIMETypes.js.map +1 -1
- package/dist/src/api/clipboard/fromClipboard/handleFileInsertion.js +4 -2
- package/dist/src/api/clipboard/fromClipboard/handleFileInsertion.js.map +1 -1
- package/dist/src/api/getBlockInfoFromPos.js +19 -25
- package/dist/src/api/getBlockInfoFromPos.js.map +1 -1
- package/dist/src/blocks/HeadingBlockContent/HeadingBlockContent.js +8 -4
- package/dist/src/blocks/HeadingBlockContent/HeadingBlockContent.js.map +1 -1
- package/dist/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.js +5 -3
- package/dist/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.js.map +1 -1
- package/dist/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.js +12 -6
- package/dist/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.js.map +1 -1
- package/dist/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.js +5 -1
- package/dist/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.js.map +1 -1
- package/dist/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.js +4 -2
- package/dist/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.js.map +1 -1
- package/dist/src/blocks/ParagraphBlockContent/ParagraphBlockContent.js +2 -1
- package/dist/src/blocks/ParagraphBlockContent/ParagraphBlockContent.js.map +1 -1
- package/dist/src/blocks/TableBlockContent/TableBlockContent.js +0 -1
- package/dist/src/blocks/TableBlockContent/TableBlockContent.js.map +1 -1
- package/dist/src/editor/BlockNoteEditor.js +39 -43
- package/dist/src/editor/BlockNoteEditor.js.map +1 -1
- package/dist/src/editor/BlockNoteEditor.test.js +2 -2
- package/dist/src/editor/BlockNoteEditor.test.js.map +1 -1
- package/dist/src/editor/BlockNoteExtensions.js +52 -6
- package/dist/src/editor/BlockNoteExtensions.js.map +1 -1
- package/dist/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.js +36 -6
- package/dist/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.js.map +1 -1
- package/dist/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.js +35 -32
- package/dist/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.js.map +1 -1
- package/dist/src/extensions/Placeholder/PlaceholderPlugin.js +74 -71
- package/dist/src/extensions/Placeholder/PlaceholderPlugin.js.map +1 -1
- package/dist/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.js +153 -149
- package/dist/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/webpack-stats.json +1 -1
- package/package.json +2 -2
- package/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap +0 -6
- package/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap +0 -5
- package/src/api/blockManipulation/commands/moveBlock/__snapshots__/moveBlock.test.ts.snap +0 -8
- package/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts +7 -3
- package/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap +439 -2
- package/src/api/blockManipulation/commands/removeBlocks/removeBlocks.test.ts +6 -0
- package/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts +2 -82
- package/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap +0 -8
- package/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts +96 -20
- package/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap +0 -6
- package/src/api/blockManipulation/commands/splitBlock/splitBlock.ts +2 -5
- package/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap +0 -490
- package/src/api/clipboard/fromClipboard/acceptedMIMETypes.ts +1 -1
- package/src/api/clipboard/fromClipboard/handleFileInsertion.ts +6 -5
- package/src/api/getBlockInfoFromPos.ts +20 -30
- package/src/api/parsers/html/__snapshots__/parse-notion-html.json +1 -2
- package/src/blocks/HeadingBlockContent/HeadingBlockContent.ts +16 -4
- package/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +9 -3
- package/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts +22 -6
- package/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts +5 -3
- package/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +8 -2
- package/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts +4 -1
- package/src/blocks/TableBlockContent/TableBlockContent.ts +0 -1
- package/src/editor/BlockNoteEditor.test.ts +2 -5
- package/src/editor/BlockNoteEditor.ts +71 -42
- package/src/editor/BlockNoteExtensions.ts +90 -14
- package/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +36 -9
- package/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.ts +45 -42
- package/src/extensions/Placeholder/PlaceholderPlugin.ts +94 -90
- package/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.ts +173 -169
- package/types/src/api/blockManipulation/commands/removeBlocks/removeBlocks.d.ts +0 -3
- package/types/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.d.ts +4 -0
- package/types/src/api/blockManipulation/setupTestEnv.d.ts +0 -6
- package/types/src/api/clipboard/fromClipboard/acceptedMIMETypes.d.ts +1 -1
- package/types/src/api/getBlockInfoFromPos.d.ts +9 -34
- package/types/src/api/testUtil/cases/customBlocks.d.ts +0 -6
- package/types/src/api/testUtil/cases/customInlineContent.d.ts +0 -6
- package/types/src/api/testUtil/cases/customStyles.d.ts +0 -6
- package/types/src/blocks/TableBlockContent/TableBlockContent.d.ts +0 -9
- package/types/src/blocks/defaultBlocks.d.ts +0 -12
- package/types/src/editor/BlockNoteEditor.d.ts +19 -2
- package/types/src/editor/BlockNoteExtensions.d.ts +14 -7
- package/types/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.d.ts +4 -1
- package/types/src/extensions/Placeholder/PlaceholderPlugin.d.ts +4 -1
- package/types/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.d.ts +4 -5
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AnyExtension, Extension, extensions } from "@tiptap/core";
|
|
2
2
|
|
|
3
|
-
import type { BlockNoteEditor } from "./BlockNoteEditor.js";
|
|
3
|
+
import type { BlockNoteEditor, BlockNoteExtension } from "./BlockNoteEditor.js";
|
|
4
4
|
|
|
5
5
|
import Collaboration from "@tiptap/extension-collaboration";
|
|
6
6
|
import CollaborationCursor from "@tiptap/extension-collaboration-cursor";
|
|
@@ -9,12 +9,22 @@ import { HardBreak } from "@tiptap/extension-hard-break";
|
|
|
9
9
|
import { History } from "@tiptap/extension-history";
|
|
10
10
|
import { Link } from "@tiptap/extension-link";
|
|
11
11
|
import { Text } from "@tiptap/extension-text";
|
|
12
|
+
import { Plugin } from "prosemirror-state";
|
|
12
13
|
import * as Y from "yjs";
|
|
13
14
|
import { createDropFileExtension } from "../api/clipboard/fromClipboard/fileDropExtension.js";
|
|
14
15
|
import { createPasteFromClipboardExtension } from "../api/clipboard/fromClipboard/pasteExtension.js";
|
|
15
16
|
import { createCopyToClipboardExtension } from "../api/clipboard/toClipboard/copyExtension.js";
|
|
16
17
|
import { BackgroundColorExtension } from "../extensions/BackgroundColor/BackgroundColorExtension.js";
|
|
18
|
+
import { FilePanelProsemirrorPlugin } from "../extensions/FilePanel/FilePanelPlugin.js";
|
|
19
|
+
import { FormattingToolbarProsemirrorPlugin } from "../extensions/FormattingToolbar/FormattingToolbarPlugin.js";
|
|
17
20
|
import { KeyboardShortcutsExtension } from "../extensions/KeyboardShortcuts/KeyboardShortcutsExtension.js";
|
|
21
|
+
import { LinkToolbarProsemirrorPlugin } from "../extensions/LinkToolbar/LinkToolbarPlugin.js";
|
|
22
|
+
import { NodeSelectionKeyboardPlugin } from "../extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.js";
|
|
23
|
+
import { PlaceholderPlugin } from "../extensions/Placeholder/PlaceholderPlugin.js";
|
|
24
|
+
import { PreviousBlockTypePlugin } from "../extensions/PreviousBlockType/PreviousBlockTypePlugin.js";
|
|
25
|
+
import { SideMenuProsemirrorPlugin } from "../extensions/SideMenu/SideMenuPlugin.js";
|
|
26
|
+
import { SuggestionMenuProseMirrorPlugin } from "../extensions/SuggestionMenu/SuggestionPlugin.js";
|
|
27
|
+
import { TableHandlesProsemirrorPlugin } from "../extensions/TableHandles/TableHandlesPlugin.js";
|
|
18
28
|
import { TextAlignmentExtension } from "../extensions/TextAlignment/TextAlignmentExtension.js";
|
|
19
29
|
import { TextColorExtension } from "../extensions/TextColor/TextColorExtension.js";
|
|
20
30
|
import { TrailingNode } from "../extensions/TrailingNode/TrailingNodeExtension.js";
|
|
@@ -30,14 +40,11 @@ import {
|
|
|
30
40
|
StyleSpecs,
|
|
31
41
|
} from "../schema/index.js";
|
|
32
42
|
|
|
33
|
-
|
|
34
|
-
* Get all the Tiptap extensions BlockNote is configured with by default
|
|
35
|
-
*/
|
|
36
|
-
export const getBlockNoteExtensions = <
|
|
43
|
+
type ExtensionOptions<
|
|
37
44
|
BSchema extends BlockSchema,
|
|
38
45
|
I extends InlineContentSchema,
|
|
39
46
|
S extends StyleSchema
|
|
40
|
-
>
|
|
47
|
+
> = {
|
|
41
48
|
editor: BlockNoteEditor<BSchema, I, S>;
|
|
42
49
|
domAttributes: Partial<BlockNoteDOMAttributes>;
|
|
43
50
|
blockSpecs: BlockSpecs;
|
|
@@ -56,8 +63,77 @@ export const getBlockNoteExtensions = <
|
|
|
56
63
|
};
|
|
57
64
|
disableExtensions: string[] | undefined;
|
|
58
65
|
setIdAttribute?: boolean;
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
animations: boolean;
|
|
67
|
+
tableHandles: boolean;
|
|
68
|
+
dropCursor: (opts: any) => Plugin;
|
|
69
|
+
placeholders: Record<string | "default", string>;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get all the Tiptap extensions BlockNote is configured with by default
|
|
74
|
+
*/
|
|
75
|
+
export const getBlockNoteExtensions = <
|
|
76
|
+
BSchema extends BlockSchema,
|
|
77
|
+
I extends InlineContentSchema,
|
|
78
|
+
S extends StyleSchema
|
|
79
|
+
>(
|
|
80
|
+
opts: ExtensionOptions<BSchema, I, S>
|
|
81
|
+
) => {
|
|
82
|
+
const ret: Record<string, BlockNoteExtension> = {};
|
|
83
|
+
const tiptapExtensions = getTipTapExtensions(opts);
|
|
84
|
+
|
|
85
|
+
for (const ext of tiptapExtensions) {
|
|
86
|
+
ret[ext.name] = ext;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Note: this is pretty hardcoded and will break when user provides plugins with same keys.
|
|
90
|
+
// Define name on plugins instead and not make this a map?
|
|
91
|
+
ret["formattingToolbar"] = new FormattingToolbarProsemirrorPlugin(
|
|
92
|
+
opts.editor
|
|
93
|
+
);
|
|
94
|
+
ret["linkToolbar"] = new LinkToolbarProsemirrorPlugin(opts.editor);
|
|
95
|
+
ret["sideMenu"] = new SideMenuProsemirrorPlugin(opts.editor);
|
|
96
|
+
ret["suggestionMenus"] = new SuggestionMenuProseMirrorPlugin(opts.editor);
|
|
97
|
+
ret["filePanel"] = new FilePanelProsemirrorPlugin(opts.editor as any);
|
|
98
|
+
ret["placeholder"] = new PlaceholderPlugin(opts.editor, opts.placeholders);
|
|
99
|
+
|
|
100
|
+
if (opts.animations ?? true) {
|
|
101
|
+
ret["animations"] = new PreviousBlockTypePlugin();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (opts.tableHandles) {
|
|
105
|
+
ret["tableHandles"] = new TableHandlesProsemirrorPlugin(opts.editor as any);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
ret["dropCursor"] = {
|
|
109
|
+
plugin: opts.dropCursor({
|
|
110
|
+
width: 5,
|
|
111
|
+
color: "#ddeeff",
|
|
112
|
+
editor: opts.editor,
|
|
113
|
+
}),
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
ret["nodeSelectionKeyboard"] = new NodeSelectionKeyboardPlugin();
|
|
117
|
+
|
|
118
|
+
const disableExtensions: string[] = opts.disableExtensions || [];
|
|
119
|
+
for (const ext of Object.keys(disableExtensions)) {
|
|
120
|
+
delete ret[ext];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return ret;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get all the Tiptap extensions BlockNote is configured with by default
|
|
128
|
+
*/
|
|
129
|
+
const getTipTapExtensions = <
|
|
130
|
+
BSchema extends BlockSchema,
|
|
131
|
+
I extends InlineContentSchema,
|
|
132
|
+
S extends StyleSchema
|
|
133
|
+
>(
|
|
134
|
+
opts: ExtensionOptions<BSchema, I, S>
|
|
135
|
+
) => {
|
|
136
|
+
const tiptapExtensions: AnyExtension[] = [
|
|
61
137
|
extensions.ClipboardTextSerializer,
|
|
62
138
|
extensions.Commands,
|
|
63
139
|
extensions.Editable,
|
|
@@ -81,6 +157,7 @@ export const getBlockNoteExtensions = <
|
|
|
81
157
|
|
|
82
158
|
// marks:
|
|
83
159
|
Link.extend({
|
|
160
|
+
inclusive: false,
|
|
84
161
|
addKeyboardShortcuts() {
|
|
85
162
|
return {
|
|
86
163
|
"Mod-k": () => {
|
|
@@ -163,7 +240,7 @@ export const getBlockNoteExtensions = <
|
|
|
163
240
|
];
|
|
164
241
|
|
|
165
242
|
if (opts.collaboration) {
|
|
166
|
-
|
|
243
|
+
tiptapExtensions.push(
|
|
167
244
|
Collaboration.configure({
|
|
168
245
|
fragment: opts.collaboration.fragment,
|
|
169
246
|
})
|
|
@@ -188,7 +265,7 @@ export const getBlockNoteExtensions = <
|
|
|
188
265
|
cursor.insertBefore(nonbreakingSpace2, null);
|
|
189
266
|
return cursor;
|
|
190
267
|
};
|
|
191
|
-
|
|
268
|
+
tiptapExtensions.push(
|
|
192
269
|
CollaborationCursor.configure({
|
|
193
270
|
user: opts.collaboration.user,
|
|
194
271
|
render: opts.collaboration.renderCursor || defaultRender,
|
|
@@ -198,9 +275,8 @@ export const getBlockNoteExtensions = <
|
|
|
198
275
|
}
|
|
199
276
|
} else {
|
|
200
277
|
// disable history extension when collaboration is enabled as Yjs takes care of undo / redo
|
|
201
|
-
|
|
278
|
+
tiptapExtensions.push(History);
|
|
202
279
|
}
|
|
203
280
|
|
|
204
|
-
|
|
205
|
-
return ret.filter((ex) => !disableExtensions.includes(ex.name));
|
|
281
|
+
return tiptapExtensions;
|
|
206
282
|
};
|
|
@@ -33,6 +33,9 @@ export const KeyboardShortcutsExtension = Extension.create<{
|
|
|
33
33
|
() =>
|
|
34
34
|
commands.command(({ state }) => {
|
|
35
35
|
const blockInfo = getBlockInfoFromSelection(state);
|
|
36
|
+
if (!blockInfo.isBlockContainer) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
36
39
|
|
|
37
40
|
const selectionAtBlockStart =
|
|
38
41
|
state.selection.from === blockInfo.blockContent.beforePos + 1;
|
|
@@ -57,7 +60,11 @@ export const KeyboardShortcutsExtension = Extension.create<{
|
|
|
57
60
|
// Removes a level of nesting if the block is indented if the selection is at the start of the block.
|
|
58
61
|
() =>
|
|
59
62
|
commands.command(({ state }) => {
|
|
60
|
-
const
|
|
63
|
+
const blockInfo = getBlockInfoFromSelection(state);
|
|
64
|
+
if (!blockInfo.isBlockContainer) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
const { blockContent } = blockInfo;
|
|
61
68
|
|
|
62
69
|
const selectionAtBlockStart =
|
|
63
70
|
state.selection.from === blockContent.beforePos + 1;
|
|
@@ -72,8 +79,11 @@ export const KeyboardShortcutsExtension = Extension.create<{
|
|
|
72
79
|
// block. The target block for merging must contain inline content.
|
|
73
80
|
() =>
|
|
74
81
|
commands.command(({ state }) => {
|
|
75
|
-
const
|
|
76
|
-
|
|
82
|
+
const blockInfo = getBlockInfoFromSelection(state);
|
|
83
|
+
if (!blockInfo.isBlockContainer) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
const { bnBlock: blockContainer, blockContent } = blockInfo;
|
|
77
87
|
|
|
78
88
|
const selectionAtBlockStart =
|
|
79
89
|
state.selection.from === blockContent.beforePos + 1;
|
|
@@ -94,6 +104,9 @@ export const KeyboardShortcutsExtension = Extension.create<{
|
|
|
94
104
|
commands.command(({ state, dispatch }) => {
|
|
95
105
|
// when at the start of a first block in a column
|
|
96
106
|
const blockInfo = getBlockInfoFromSelection(state);
|
|
107
|
+
if (!blockInfo.isBlockContainer) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
97
110
|
|
|
98
111
|
const selectionAtBlockStart =
|
|
99
112
|
state.selection.from === blockInfo.blockContent.beforePos + 1;
|
|
@@ -315,11 +328,15 @@ export const KeyboardShortcutsExtension = Extension.create<{
|
|
|
315
328
|
() =>
|
|
316
329
|
commands.command(({ state }) => {
|
|
317
330
|
// TODO: Change this to not rely on offsets & schema assumptions
|
|
331
|
+
const blockInfo = getBlockInfoFromSelection(state);
|
|
332
|
+
if (!blockInfo.isBlockContainer) {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
318
335
|
const {
|
|
319
336
|
bnBlock: blockContainer,
|
|
320
337
|
blockContent,
|
|
321
338
|
childContainer,
|
|
322
|
-
} =
|
|
339
|
+
} = blockInfo;
|
|
323
340
|
|
|
324
341
|
const { depth } = state.doc.resolve(blockContainer.beforePos);
|
|
325
342
|
const blockAtDocEnd =
|
|
@@ -358,8 +375,11 @@ export const KeyboardShortcutsExtension = Extension.create<{
|
|
|
358
375
|
// of the block.
|
|
359
376
|
() =>
|
|
360
377
|
commands.command(({ state }) => {
|
|
361
|
-
const
|
|
362
|
-
|
|
378
|
+
const blockInfo = getBlockInfoFromSelection(state);
|
|
379
|
+
if (!blockInfo.isBlockContainer) {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
const { bnBlock: blockContainer, blockContent } = blockInfo;
|
|
363
383
|
|
|
364
384
|
const { depth } = state.doc.resolve(blockContainer.beforePos);
|
|
365
385
|
|
|
@@ -385,8 +405,11 @@ export const KeyboardShortcutsExtension = Extension.create<{
|
|
|
385
405
|
// empty & at the start of the block.
|
|
386
406
|
() =>
|
|
387
407
|
commands.command(({ state, dispatch }) => {
|
|
388
|
-
const
|
|
389
|
-
|
|
408
|
+
const blockInfo = getBlockInfoFromSelection(state);
|
|
409
|
+
if (!blockInfo.isBlockContainer) {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
const { bnBlock: blockContainer, blockContent } = blockInfo;
|
|
390
413
|
|
|
391
414
|
const selectionAtBlockStart =
|
|
392
415
|
state.selection.$anchor.parentOffset === 0;
|
|
@@ -419,7 +442,11 @@ export const KeyboardShortcutsExtension = Extension.create<{
|
|
|
419
442
|
// deletes the selection beforehand, if it's not empty.
|
|
420
443
|
() =>
|
|
421
444
|
commands.command(({ state, chain }) => {
|
|
422
|
-
const
|
|
445
|
+
const blockInfo = getBlockInfoFromSelection(state);
|
|
446
|
+
if (!blockInfo.isBlockContainer) {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
const { blockContent } = blockInfo;
|
|
423
450
|
|
|
424
451
|
const selectionAtBlockStart =
|
|
425
452
|
state.selection.$anchor.parentOffset === 0;
|
|
@@ -15,51 +15,54 @@ const PLUGIN_KEY = new PluginKey("node-selection-keyboard");
|
|
|
15
15
|
// While a more elegant solution would probably process transactions instead of
|
|
16
16
|
// keystrokes, this brings us most of the way to Notion's UX without much added
|
|
17
17
|
// complexity.
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// Checks
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
18
|
+
export class NodeSelectionKeyboardPlugin {
|
|
19
|
+
public readonly plugin: Plugin;
|
|
20
|
+
constructor() {
|
|
21
|
+
this.plugin = new Plugin({
|
|
22
|
+
key: PLUGIN_KEY,
|
|
23
|
+
props: {
|
|
24
|
+
handleKeyDown: (view, event) => {
|
|
25
|
+
// Checks for node selection
|
|
26
|
+
if ("node" in view.state.selection) {
|
|
27
|
+
// Checks if key press uses ctrl/meta modifier
|
|
28
|
+
if (event.ctrlKey || event.metaKey) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
// Checks if key press is alphanumeric
|
|
32
|
+
if (event.key.length === 1) {
|
|
33
|
+
event.preventDefault();
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
// Checks if key press is Enter
|
|
38
|
+
if (
|
|
39
|
+
event.key === "Enter" &&
|
|
40
|
+
!event.shiftKey &&
|
|
41
|
+
!event.altKey &&
|
|
42
|
+
!event.ctrlKey &&
|
|
43
|
+
!event.metaKey
|
|
44
|
+
) {
|
|
45
|
+
const tr = view.state.tr;
|
|
46
|
+
view.dispatch(
|
|
47
|
+
tr
|
|
48
|
+
.insert(
|
|
49
|
+
view.state.tr.selection.$to.after(),
|
|
50
|
+
view.state.schema.nodes["paragraph"].createChecked()
|
|
51
|
+
)
|
|
52
|
+
.setSelection(
|
|
53
|
+
new TextSelection(
|
|
54
|
+
tr.doc.resolve(view.state.tr.selection.$to.after() + 1)
|
|
55
|
+
)
|
|
53
56
|
)
|
|
54
|
-
|
|
55
|
-
);
|
|
57
|
+
);
|
|
56
58
|
|
|
57
|
-
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
58
61
|
}
|
|
59
|
-
}
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
return false;
|
|
64
|
+
},
|
|
62
65
|
},
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -4,110 +4,114 @@ import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
|
|
|
4
4
|
|
|
5
5
|
const PLUGIN_KEY = new PluginKey(`blocknote-placeholder`);
|
|
6
6
|
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
editor._tiptapEditor.view.root.append(styleEl);
|
|
21
|
-
} else {
|
|
22
|
-
editor._tiptapEditor.view.root.head.appendChild(styleEl);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const styleSheet = styleEl.sheet!;
|
|
26
|
-
|
|
27
|
-
const getBaseSelector = (additionalSelectors = "") =>
|
|
28
|
-
`.bn-block-content${additionalSelectors} .bn-inline-content:has(> .ProseMirror-trailingBreak:only-child):before`;
|
|
29
|
-
|
|
30
|
-
const getSelector = (
|
|
31
|
-
blockType: string | "default",
|
|
32
|
-
mustBeFocused = true
|
|
33
|
-
) => {
|
|
34
|
-
const mustBeFocusedSelector = mustBeFocused
|
|
35
|
-
? `[data-is-empty-and-focused]`
|
|
36
|
-
: ``;
|
|
37
|
-
|
|
38
|
-
if (blockType === "default") {
|
|
39
|
-
return getBaseSelector(mustBeFocusedSelector);
|
|
7
|
+
export class PlaceholderPlugin {
|
|
8
|
+
public readonly plugin: Plugin;
|
|
9
|
+
constructor(
|
|
10
|
+
editor: BlockNoteEditor<any, any, any>,
|
|
11
|
+
placeholders: Record<string | "default", string>
|
|
12
|
+
) {
|
|
13
|
+
this.plugin = new Plugin({
|
|
14
|
+
key: PLUGIN_KEY,
|
|
15
|
+
view: () => {
|
|
16
|
+
const styleEl = document.createElement("style");
|
|
17
|
+
const nonce = editor._tiptapEditor.options.injectNonce;
|
|
18
|
+
if (nonce) {
|
|
19
|
+
styleEl.setAttribute("nonce", nonce);
|
|
40
20
|
}
|
|
21
|
+
if (editor._tiptapEditor.view.root instanceof ShadowRoot) {
|
|
22
|
+
editor._tiptapEditor.view.root.append(styleEl);
|
|
23
|
+
} else {
|
|
24
|
+
editor._tiptapEditor.view.root.head.appendChild(styleEl);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const styleSheet = styleEl.sheet!;
|
|
28
|
+
|
|
29
|
+
const getBaseSelector = (additionalSelectors = "") =>
|
|
30
|
+
`.bn-block-content${additionalSelectors} .bn-inline-content:has(> .ProseMirror-trailingBreak:only-child):before`;
|
|
41
31
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
32
|
+
const getSelector = (
|
|
33
|
+
blockType: string | "default",
|
|
34
|
+
mustBeFocused = true
|
|
35
|
+
) => {
|
|
36
|
+
const mustBeFocusedSelector = mustBeFocused
|
|
37
|
+
? `[data-is-empty-and-focused]`
|
|
38
|
+
: ``;
|
|
39
|
+
|
|
40
|
+
if (blockType === "default") {
|
|
41
|
+
return getBaseSelector(mustBeFocusedSelector);
|
|
42
|
+
}
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
const blockTypeSelector = `[data-content-type="${blockType}"]`;
|
|
45
|
+
return getBaseSelector(mustBeFocusedSelector + blockTypeSelector);
|
|
46
|
+
};
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
placeholder
|
|
52
|
-
)}; }`
|
|
53
|
-
);
|
|
48
|
+
for (const [blockType, placeholder] of Object.entries(placeholders)) {
|
|
49
|
+
const mustBeFocused = blockType === "default";
|
|
54
50
|
|
|
55
|
-
// For some reason, the placeholders which show when the block is focused
|
|
56
|
-
// take priority over ones which show depending on block type, so we need
|
|
57
|
-
// to make sure the block specific ones are also used when the block is
|
|
58
|
-
// focused.
|
|
59
|
-
if (!mustBeFocused) {
|
|
60
51
|
styleSheet.insertRule(
|
|
61
|
-
`${getSelector(
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
`${getSelector(
|
|
53
|
+
blockType,
|
|
54
|
+
mustBeFocused
|
|
55
|
+
)}{ content: ${JSON.stringify(placeholder)}; }`
|
|
64
56
|
);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
57
|
+
|
|
58
|
+
// For some reason, the placeholders which show when the block is focused
|
|
59
|
+
// take priority over ones which show depending on block type, so we need
|
|
60
|
+
// to make sure the block specific ones are also used when the block is
|
|
61
|
+
// focused.
|
|
62
|
+
if (!mustBeFocused) {
|
|
63
|
+
styleSheet.insertRule(
|
|
64
|
+
`${getSelector(blockType, true)}{ content: ${JSON.stringify(
|
|
65
|
+
placeholder
|
|
66
|
+
)}; }`
|
|
67
|
+
);
|
|
74
68
|
}
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
},
|
|
78
|
-
props: {
|
|
79
|
-
// TODO: maybe also add placeholder for empty document ("e.g.: start writing..")
|
|
80
|
-
decorations: (state) => {
|
|
81
|
-
const { doc, selection } = state;
|
|
82
|
-
|
|
83
|
-
if (!editor.isEditable) {
|
|
84
|
-
return;
|
|
85
69
|
}
|
|
86
70
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
71
|
+
return {
|
|
72
|
+
destroy: () => {
|
|
73
|
+
if (editor._tiptapEditor.view.root instanceof ShadowRoot) {
|
|
74
|
+
editor._tiptapEditor.view.root.removeChild(styleEl);
|
|
75
|
+
} else {
|
|
76
|
+
editor._tiptapEditor.view.root.head.removeChild(styleEl);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
props: {
|
|
82
|
+
// TODO: maybe also add placeholder for empty document ("e.g.: start writing..")
|
|
83
|
+
decorations: (state) => {
|
|
84
|
+
const { doc, selection } = state;
|
|
90
85
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
86
|
+
if (!editor.isEditable) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
95
89
|
|
|
96
|
-
|
|
97
|
-
|
|
90
|
+
if (!selection.empty) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
98
93
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
// Don't show placeholder when the cursor is inside a code block
|
|
95
|
+
if (selection.$from.parent.type.spec.code) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
102
98
|
|
|
103
|
-
|
|
99
|
+
const $pos = selection.$anchor;
|
|
100
|
+
const node = $pos.parent;
|
|
104
101
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
102
|
+
if (node.content.size > 0) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const before = $pos.before();
|
|
108
107
|
|
|
109
|
-
|
|
108
|
+
const dec = Decoration.node(before, before + node.nodeSize, {
|
|
109
|
+
"data-is-empty-and-focused": "true",
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return DecorationSet.create(doc, [dec]);
|
|
113
|
+
},
|
|
110
114
|
},
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|