@blocknote/core 0.42.3 → 0.44.0

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.
Files changed (200) hide show
  1. package/dist/BlockNoteExtension-BWw0r8Gy.cjs +2 -0
  2. package/dist/BlockNoteExtension-BWw0r8Gy.cjs.map +1 -0
  3. package/dist/BlockNoteExtension-C2X7LW-V.js +25 -0
  4. package/dist/BlockNoteExtension-C2X7LW-V.js.map +1 -0
  5. package/dist/BlockNoteSchema-B4gm-Qco.cjs +2 -0
  6. package/dist/BlockNoteSchema-B4gm-Qco.cjs.map +1 -0
  7. package/dist/BlockNoteSchema-C-l154WP.js +270 -0
  8. package/dist/BlockNoteSchema-C-l154WP.js.map +1 -0
  9. package/dist/EventEmitter-CLwfmbqG.cjs +2 -0
  10. package/dist/EventEmitter-CLwfmbqG.cjs.map +1 -0
  11. package/dist/EventEmitter-CjSwpTbz.js +27 -0
  12. package/dist/EventEmitter-CjSwpTbz.js.map +1 -0
  13. package/dist/ShowSelection-BW37oJ6h.cjs +2 -0
  14. package/dist/ShowSelection-BW37oJ6h.cjs.map +1 -0
  15. package/dist/ShowSelection-Dz-NEase.js +43 -0
  16. package/dist/ShowSelection-Dz-NEase.js.map +1 -0
  17. package/dist/TrailingNode-B_zPMWxw.js +2098 -0
  18. package/dist/TrailingNode-B_zPMWxw.js.map +1 -0
  19. package/dist/TrailingNode-CRHrgOnK.cjs +2 -0
  20. package/dist/TrailingNode-CRHrgOnK.cjs.map +1 -0
  21. package/dist/{blockToNode-DIfPWLH8.js → blockToNode-DBNbhwwC.js} +33 -33
  22. package/dist/blockToNode-DBNbhwwC.js.map +1 -0
  23. package/dist/blockToNode-w7H99R6p.cjs.map +1 -1
  24. package/dist/blocknote.cjs +4 -4
  25. package/dist/blocknote.cjs.map +1 -1
  26. package/dist/blocknote.js +2496 -5686
  27. package/dist/blocknote.js.map +1 -1
  28. package/dist/blocks.cjs +1 -1
  29. package/dist/blocks.js +71 -70
  30. package/dist/blocks.js.map +1 -1
  31. package/dist/comments.cjs +1 -1
  32. package/dist/comments.cjs.map +1 -1
  33. package/dist/comments.js +451 -137
  34. package/dist/comments.js.map +1 -1
  35. package/dist/defaultBlocks-DLJ4Q1_J.cjs +6 -0
  36. package/dist/defaultBlocks-DLJ4Q1_J.cjs.map +1 -0
  37. package/dist/{BlockNoteSchema-Bi-eeHal.js → defaultBlocks-DgA_mtQV.js} +974 -1027
  38. package/dist/defaultBlocks-DgA_mtQV.js.map +1 -0
  39. package/dist/extensions.cjs +2 -0
  40. package/dist/extensions.cjs.map +1 -0
  41. package/dist/extensions.js +57 -0
  42. package/dist/extensions.js.map +1 -0
  43. package/dist/tsconfig.tsbuildinfo +1 -1
  44. package/dist/webpack-stats.json +1 -1
  45. package/dist/yjs.js +1 -1
  46. package/package.json +9 -3
  47. package/src/api/nodeConversions/blockToNode.ts +1 -1
  48. package/src/api/nodeConversions/nodeToBlock.ts +1 -1
  49. package/src/blocks/Code/block.ts +4 -4
  50. package/src/blocks/Divider/block.ts +2 -2
  51. package/src/blocks/File/helpers/render/createAddFileButton.ts +7 -5
  52. package/src/blocks/Heading/block.ts +23 -20
  53. package/src/blocks/ListItem/BulletListItem/block.ts +2 -2
  54. package/src/blocks/ListItem/CheckListItem/block.ts +2 -2
  55. package/src/blocks/ListItem/NumberedListItem/block.ts +3 -3
  56. package/src/blocks/ListItem/ToggleListItem/block.ts +2 -2
  57. package/src/blocks/PageBreak/getPageBreakSlashMenuItems.ts +2 -2
  58. package/src/blocks/Paragraph/block.ts +2 -2
  59. package/src/blocks/Quote/block.ts +2 -2
  60. package/src/blocks/Table/block.ts +4 -3
  61. package/src/blocks/ToggleWrapper/createToggleWrapper.ts +2 -1
  62. package/src/comments/extension.ts +353 -0
  63. package/src/comments/index.ts +2 -1
  64. package/src/comments/types.ts +8 -0
  65. package/src/{extensions/Comments → comments}/userstore/UserStore.ts +2 -2
  66. package/src/editor/BlockNoteEditor.test.ts +2 -23
  67. package/src/editor/BlockNoteEditor.ts +60 -453
  68. package/src/editor/BlockNoteExtension.test.ts +103 -0
  69. package/src/editor/BlockNoteExtension.ts +174 -56
  70. package/src/editor/managers/EventManager.ts +64 -35
  71. package/src/editor/managers/ExtensionManager/extensions.ts +214 -0
  72. package/src/editor/managers/ExtensionManager/index.ts +514 -0
  73. package/src/editor/managers/ExtensionManager/symbol.ts +6 -0
  74. package/src/editor/managers/SelectionManager.ts +5 -1
  75. package/src/editor/managers/StateManager.ts +29 -17
  76. package/src/editor/managers/index.ts +1 -5
  77. package/src/extensions/BlockChange/{BlockChangePlugin.ts → BlockChange.ts} +27 -29
  78. package/src/extensions/Collaboration/{ForkYDocPlugin.test.ts → ForkYDoc.test.ts} +6 -5
  79. package/src/extensions/Collaboration/ForkYDoc.ts +158 -0
  80. package/src/extensions/Collaboration/YCursorPlugin.ts +183 -0
  81. package/src/extensions/Collaboration/YSync.ts +16 -0
  82. package/src/extensions/Collaboration/YUndo.ts +12 -0
  83. package/src/extensions/Collaboration/schemaMigration/SchemaMigration.ts +59 -0
  84. package/src/extensions/DropCursor/DropCursor.ts +26 -0
  85. package/src/extensions/FilePanel/FilePanel.ts +41 -0
  86. package/src/extensions/FormattingToolbar/FormattingToolbar.ts +119 -0
  87. package/src/extensions/History/History.ts +11 -0
  88. package/src/extensions/LinkToolbar/LinkToolbar.ts +121 -0
  89. package/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboard.ts +74 -0
  90. package/src/extensions/Placeholder/Placeholder.ts +148 -0
  91. package/src/extensions/PreviousBlockType/{PreviousBlockTypePlugin.ts → PreviousBlockType.ts} +9 -13
  92. package/src/extensions/ShowSelection/{ShowSelectionPlugin.ts → ShowSelection.ts} +27 -33
  93. package/src/extensions/SideMenu/{SideMenuPlugin.ts → SideMenu.ts} +63 -83
  94. package/src/extensions/SuggestionMenu/{SuggestionPlugin.ts → SuggestionMenu.ts} +71 -77
  95. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +29 -44
  96. package/src/extensions/TableHandles/{TableHandlesPlugin.ts → TableHandles.ts} +416 -437
  97. package/src/extensions/TrailingNode/{TrailingNodeExtension.ts → TrailingNode.ts} +8 -17
  98. package/src/extensions/index.ts +24 -0
  99. package/src/extensions/{BackgroundColor → tiptap-extensions/BackgroundColor}/BackgroundColorExtension.ts +1 -1
  100. package/src/extensions/{KeyboardShortcuts → tiptap-extensions/KeyboardShortcuts}/KeyboardShortcutsExtension.ts +21 -16
  101. package/src/extensions/{TextColor → tiptap-extensions/TextColor}/TextColorExtension.ts +1 -1
  102. package/src/extensions/tiptap-extensions/index.ts +31 -0
  103. package/src/index.ts +1 -13
  104. package/src/schema/blocks/createSpec.ts +14 -11
  105. package/src/schema/blocks/internal.ts +2 -2
  106. package/src/schema/blocks/types.ts +8 -5
  107. package/src/schema/schema.ts +11 -36
  108. package/src/util/topo-sort.ts +46 -0
  109. package/types/src/comments/extension.d.ts +70 -0
  110. package/types/src/comments/index.d.ts +2 -1
  111. package/types/src/comments/types.d.ts +8 -0
  112. package/types/src/{extensions/Comments → comments}/userstore/UserStore.d.ts +2 -2
  113. package/types/src/editor/BlockNoteEditor.d.ts +34 -105
  114. package/types/src/editor/BlockNoteExtension.d.ts +87 -22
  115. package/types/src/editor/managers/EventManager.d.ts +25 -16
  116. package/types/src/editor/managers/ExtensionManager/extensions.d.ts +8 -0
  117. package/types/src/editor/managers/ExtensionManager/index.d.ts +83 -0
  118. package/types/src/editor/managers/ExtensionManager/symbol.d.ts +5 -0
  119. package/types/src/editor/managers/StateManager.d.ts +1 -12
  120. package/types/src/editor/managers/index.d.ts +1 -2
  121. package/types/src/extensions/BlockChange/BlockChange.d.ts +16 -0
  122. package/types/src/extensions/Collaboration/ForkYDoc.d.ts +34 -0
  123. package/types/src/extensions/Collaboration/ForkYDoc.test.d.ts +1 -0
  124. package/types/src/extensions/Collaboration/YCursorPlugin.d.ts +24 -0
  125. package/types/src/extensions/Collaboration/YSync.d.ts +8 -0
  126. package/types/src/extensions/Collaboration/YUndo.d.ts +12 -0
  127. package/types/src/extensions/Collaboration/schemaMigration/SchemaMigration.d.ts +8 -0
  128. package/types/src/extensions/DropCursor/DropCursor.d.ts +5 -0
  129. package/types/src/extensions/FilePanel/FilePanel.d.ts +11 -0
  130. package/types/src/extensions/FormattingToolbar/FormattingToolbar.d.ts +9 -0
  131. package/types/src/extensions/History/History.d.ts +6 -0
  132. package/types/src/extensions/LinkToolbar/LinkToolbar.d.ts +24 -0
  133. package/types/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboard.d.ts +5 -0
  134. package/types/src/extensions/Placeholder/Placeholder.d.ts +6 -0
  135. package/types/src/extensions/PreviousBlockType/{PreviousBlockTypePlugin.d.ts → PreviousBlockType.d.ts} +9 -5
  136. package/types/src/extensions/ShowSelection/ShowSelection.d.ts +21 -0
  137. package/types/src/extensions/SideMenu/{SideMenuPlugin.d.ts → SideMenu.d.ts} +11 -15
  138. package/types/src/extensions/SuggestionMenu/SuggestionMenu.d.ts +54 -0
  139. package/types/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.d.ts +1 -1
  140. package/types/src/extensions/TableHandles/{TableHandlesPlugin.d.ts → TableHandles.d.ts} +28 -31
  141. package/types/src/extensions/TrailingNode/TrailingNode.d.ts +8 -0
  142. package/types/src/extensions/index.d.ts +24 -0
  143. package/types/src/extensions/{KeyboardShortcuts → tiptap-extensions/KeyboardShortcuts}/KeyboardShortcutsExtension.d.ts +1 -1
  144. package/types/src/extensions/tiptap-extensions/index.d.ts +11 -0
  145. package/types/src/index.d.ts +1 -13
  146. package/types/src/schema/blocks/createSpec.d.ts +4 -4
  147. package/types/src/schema/blocks/internal.d.ts +2 -2
  148. package/types/src/schema/blocks/types.d.ts +5 -5
  149. package/types/src/util/topo-sort.d.ts +8 -0
  150. package/dist/BlockNoteSchema-Bi-eeHal.js.map +0 -1
  151. package/dist/BlockNoteSchema-DjDaA2C3.cjs +0 -6
  152. package/dist/BlockNoteSchema-DjDaA2C3.cjs.map +0 -1
  153. package/dist/blockToNode-DIfPWLH8.js.map +0 -1
  154. package/src/comments/models/User.ts +0 -8
  155. package/src/editor/BlockNoteExtensions.ts +0 -325
  156. package/src/editor/managers/CollaborationManager.ts +0 -212
  157. package/src/editor/managers/ExtensionManager.ts +0 -130
  158. package/src/extensions/Collaboration/CursorPlugin.ts +0 -189
  159. package/src/extensions/Collaboration/ForkYDocPlugin.ts +0 -192
  160. package/src/extensions/Collaboration/SyncPlugin.ts +0 -18
  161. package/src/extensions/Collaboration/UndoPlugin.ts +0 -18
  162. package/src/extensions/Collaboration/schemaMigration/SchemaMigrationPlugin.ts +0 -59
  163. package/src/extensions/Comments/CommentsPlugin.ts +0 -392
  164. package/src/extensions/FilePanel/FilePanelPlugin.ts +0 -206
  165. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +0 -363
  166. package/src/extensions/LinkToolbar/LinkToolbarPlugin.ts +0 -380
  167. package/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.ts +0 -75
  168. package/src/extensions/Placeholder/PlaceholderPlugin.ts +0 -147
  169. package/types/src/comments/models/User.d.ts +0 -8
  170. package/types/src/editor/BlockNoteExtensions.d.ts +0 -43
  171. package/types/src/editor/managers/CollaborationManager.d.ts +0 -115
  172. package/types/src/editor/managers/ExtensionManager.d.ts +0 -68
  173. package/types/src/extensions/BlockChange/BlockChangePlugin.d.ts +0 -15
  174. package/types/src/extensions/Collaboration/CursorPlugin.d.ts +0 -37
  175. package/types/src/extensions/Collaboration/ForkYDocPlugin.d.ts +0 -41
  176. package/types/src/extensions/Collaboration/SyncPlugin.d.ts +0 -7
  177. package/types/src/extensions/Collaboration/UndoPlugin.d.ts +0 -9
  178. package/types/src/extensions/Collaboration/schemaMigration/SchemaMigrationPlugin.d.ts +0 -7
  179. package/types/src/extensions/Comments/CommentsPlugin.d.ts +0 -66
  180. package/types/src/extensions/FilePanel/FilePanelPlugin.d.ts +0 -31
  181. package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +0 -41
  182. package/types/src/extensions/LinkToolbar/LinkToolbarPlugin.d.ts +0 -42
  183. package/types/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.d.ts +0 -5
  184. package/types/src/extensions/Placeholder/PlaceholderPlugin.d.ts +0 -6
  185. package/types/src/extensions/ShowSelection/ShowSelectionPlugin.d.ts +0 -15
  186. package/types/src/extensions/SuggestionMenu/SuggestionPlugin.d.ts +0 -31
  187. package/types/src/extensions/TrailingNode/TrailingNodeExtension.d.ts +0 -13
  188. /package/src/{extensions/Comments/CommentMark.ts → comments/mark.ts} +0 -0
  189. /package/src/extensions/{HardBreak → tiptap-extensions/HardBreak}/HardBreak.ts +0 -0
  190. /package/src/extensions/{Suggestions → tiptap-extensions/Suggestions}/SuggestionMarks.ts +0 -0
  191. /package/src/extensions/{TextAlignment → tiptap-extensions/TextAlignment}/TextAlignmentExtension.ts +0 -0
  192. /package/src/extensions/{UniqueID → tiptap-extensions/UniqueID}/UniqueID.ts +0 -0
  193. /package/types/src/{extensions/Comments/CommentMark.d.ts → comments/mark.d.ts} +0 -0
  194. /package/types/src/{extensions/Collaboration/ForkYDocPlugin.test.d.ts → editor/BlockNoteExtension.test.d.ts} +0 -0
  195. /package/types/src/extensions/{BackgroundColor → tiptap-extensions/BackgroundColor}/BackgroundColorExtension.d.ts +0 -0
  196. /package/types/src/extensions/{HardBreak → tiptap-extensions/HardBreak}/HardBreak.d.ts +0 -0
  197. /package/types/src/extensions/{Suggestions → tiptap-extensions/Suggestions}/SuggestionMarks.d.ts +0 -0
  198. /package/types/src/extensions/{TextAlignment → tiptap-extensions/TextAlignment}/TextAlignmentExtension.d.ts +0 -0
  199. /package/types/src/extensions/{TextColor → tiptap-extensions/TextColor}/TextColorExtension.d.ts +0 -0
  200. /package/types/src/extensions/{UniqueID → tiptap-extensions/UniqueID}/UniqueID.d.ts +0 -0
@@ -0,0 +1,121 @@
1
+ import { getMarkRange, posToDOMRect } from "@tiptap/core";
2
+ import { createExtension } from "../../editor/BlockNoteExtension.js";
3
+ import { getPmSchema } from "../../api/pmUtil.js";
4
+
5
+ export const LinkToolbarExtension = createExtension(({ editor }) => {
6
+ function getLinkElementAtPos(pos: number) {
7
+ let currentNode = editor.prosemirrorView.nodeDOM(pos);
8
+ while (currentNode && currentNode.parentElement) {
9
+ if (currentNode.nodeName === "A") {
10
+ return currentNode as HTMLAnchorElement;
11
+ }
12
+ currentNode = currentNode.parentElement;
13
+ }
14
+ return null;
15
+ }
16
+
17
+ function getMarkAtPos(pos: number, markType: string) {
18
+ return editor.transact((tr) => {
19
+ const resolvedPos = tr.doc.resolve(pos);
20
+ const mark = resolvedPos
21
+ .marks()
22
+ .find((mark) => mark.type.name === markType);
23
+
24
+ if (!mark) {
25
+ return;
26
+ }
27
+
28
+ const markRange = getMarkRange(resolvedPos, mark.type);
29
+ if (!markRange) {
30
+ return;
31
+ }
32
+
33
+ return {
34
+ range: markRange,
35
+ mark,
36
+ get text() {
37
+ return tr.doc.textBetween(markRange.from, markRange.to);
38
+ },
39
+ get position() {
40
+ // to minimize re-renders, we convert to JSON, which is the same shape anyway
41
+ return posToDOMRect(
42
+ editor.prosemirrorView,
43
+ markRange.from,
44
+ markRange.to,
45
+ ).toJSON() as DOMRect;
46
+ },
47
+ };
48
+ });
49
+ }
50
+
51
+ function getLinkAtSelection() {
52
+ return editor.transact((tr) => {
53
+ const selection = tr.selection;
54
+ if (!selection.empty) {
55
+ return undefined;
56
+ }
57
+ return getMarkAtPos(selection.anchor, "link");
58
+ });
59
+ }
60
+
61
+ return {
62
+ key: "linkToolbar",
63
+
64
+ getLinkAtSelection,
65
+ getLinkElementAtPos,
66
+ getMarkAtPos,
67
+
68
+ getLinkAtElement(element: HTMLElement) {
69
+ return editor.transact(() => {
70
+ const posAtElement = editor.prosemirrorView.posAtDOM(element, 0) + 1;
71
+ return getMarkAtPos(posAtElement, "link");
72
+ });
73
+ },
74
+
75
+ editLink(
76
+ url: string,
77
+ text: string,
78
+ position = editor.transact((tr) => tr.selection.anchor),
79
+ ) {
80
+ editor.transact((tr) => {
81
+ const pmSchema = getPmSchema(tr);
82
+ const { range } = getMarkAtPos(position + 1, "link") || {
83
+ range: {
84
+ from: tr.selection.from,
85
+ to: tr.selection.to,
86
+ },
87
+ };
88
+ if (!range) {
89
+ return;
90
+ }
91
+ tr.insertText(text, range.from, range.to);
92
+ tr.addMark(
93
+ range.from,
94
+ range.from + text.length,
95
+ pmSchema.mark("link", { href: url }),
96
+ );
97
+ });
98
+ editor.prosemirrorView.focus();
99
+ },
100
+ deleteLink(position = editor.transact((tr) => tr.selection.anchor)) {
101
+ editor.transact((tr) => {
102
+ const pmSchema = getPmSchema(tr);
103
+ const { range } = getMarkAtPos(position + 1, "link") || {
104
+ range: {
105
+ from: tr.selection.from,
106
+ to: tr.selection.to,
107
+ },
108
+ };
109
+ if (!range) {
110
+ return;
111
+ }
112
+
113
+ tr.removeMark(range.from, range.to, pmSchema.marks["link"]).setMeta(
114
+ "preventAutolink",
115
+ true,
116
+ );
117
+ });
118
+ editor.prosemirrorView.focus();
119
+ },
120
+ } as const;
121
+ });
@@ -0,0 +1,74 @@
1
+ import { Plugin, PluginKey, TextSelection } from "prosemirror-state";
2
+ import { createExtension } from "../../editor/BlockNoteExtension.js";
3
+
4
+ const PLUGIN_KEY = new PluginKey("node-selection-keyboard");
5
+ // By default, typing with a node selection active will cause ProseMirror to
6
+ // replace the node with one that contains editable content. This plugin blocks
7
+ // this behaviour without also blocking things like keyboard shortcuts:
8
+ //
9
+ // - Lets through key presses that do not include alphanumeric characters. This
10
+ // includes things like backspace/delete/home/end/etc.
11
+ // - Lets through any key presses that include ctrl/meta keys. These will be
12
+ // shortcuts of some kind like ctrl+C/mod+C.
13
+ // - Special case for Enter key which creates a new paragraph block below and
14
+ // sets the selection to it. This is just to bring the UX closer to Notion
15
+ //
16
+ // While a more elegant solution would probably process transactions instead of
17
+ // keystrokes, this brings us most of the way to Notion's UX without much added
18
+ // complexity.
19
+ export const NodeSelectionKeyboardExtension = createExtension(
20
+ () =>
21
+ ({
22
+ key: "nodeSelectionKeyboard",
23
+ prosemirrorPlugins: [
24
+ new Plugin({
25
+ key: PLUGIN_KEY,
26
+ props: {
27
+ handleKeyDown: (view, event) => {
28
+ // Checks for node selection
29
+ if ("node" in view.state.selection) {
30
+ // Checks if key press uses ctrl/meta modifier
31
+ if (event.ctrlKey || event.metaKey) {
32
+ return false;
33
+ }
34
+ // Checks if key press is alphanumeric
35
+ if (event.key.length === 1) {
36
+ event.preventDefault();
37
+
38
+ return true;
39
+ }
40
+ // Checks if key press is Enter
41
+ if (
42
+ event.key === "Enter" &&
43
+ !event.shiftKey &&
44
+ !event.altKey &&
45
+ !event.ctrlKey &&
46
+ !event.metaKey
47
+ ) {
48
+ const tr = view.state.tr;
49
+ view.dispatch(
50
+ tr
51
+ .insert(
52
+ view.state.tr.selection.$to.after(),
53
+ view.state.schema.nodes["paragraph"].createChecked(),
54
+ )
55
+ .setSelection(
56
+ new TextSelection(
57
+ tr.doc.resolve(
58
+ view.state.tr.selection.$to.after() + 1,
59
+ ),
60
+ ),
61
+ ),
62
+ );
63
+
64
+ return true;
65
+ }
66
+ }
67
+
68
+ return false;
69
+ },
70
+ },
71
+ }),
72
+ ],
73
+ }) as const,
74
+ );
@@ -0,0 +1,148 @@
1
+ import { Plugin, PluginKey } from "prosemirror-state";
2
+ import { Decoration, DecorationSet } from "prosemirror-view";
3
+ import { v4 } from "uuid";
4
+ import {
5
+ createExtension,
6
+ ExtensionOptions,
7
+ } from "../../editor/BlockNoteExtension.js";
8
+ import { BlockNoteEditorOptions } from "../../editor/BlockNoteEditor.js";
9
+
10
+ const PLUGIN_KEY = new PluginKey(`blocknote-placeholder`);
11
+
12
+ export const PlaceholderExtension = createExtension(
13
+ ({
14
+ editor,
15
+ options,
16
+ }: ExtensionOptions<
17
+ Pick<BlockNoteEditorOptions<any, any, any>, "placeholders">
18
+ >) => {
19
+ const placeholders = options.placeholders;
20
+ return {
21
+ key: "placeholder",
22
+ prosemirrorPlugins: [
23
+ new Plugin({
24
+ key: PLUGIN_KEY,
25
+ view: (view) => {
26
+ const uniqueEditorSelector = `placeholder-selector-${v4()}`;
27
+ view.dom.classList.add(uniqueEditorSelector);
28
+ const styleEl = document.createElement("style");
29
+
30
+ const nonce = editor._tiptapEditor.options.injectNonce;
31
+ if (nonce) {
32
+ styleEl.setAttribute("nonce", nonce);
33
+ }
34
+
35
+ if (view.root instanceof window.ShadowRoot) {
36
+ view.root.append(styleEl);
37
+ } else {
38
+ view.root.head.appendChild(styleEl);
39
+ }
40
+
41
+ const styleSheet = styleEl.sheet!;
42
+
43
+ const getSelector = (additionalSelectors = "") =>
44
+ `.${uniqueEditorSelector} .bn-block-content${additionalSelectors} .bn-inline-content:has(> .ProseMirror-trailingBreak:only-child):before`;
45
+
46
+ try {
47
+ // FIXME: the names "default" and "emptyDocument" are hardcoded
48
+ const {
49
+ default: defaultPlaceholder,
50
+ emptyDocument: emptyPlaceholder,
51
+ ...rest
52
+ } = placeholders || {};
53
+
54
+ // add block specific placeholders
55
+ for (const [blockType, placeholder] of Object.entries(rest)) {
56
+ const blockTypeSelector = `[data-content-type="${blockType}"]`;
57
+
58
+ styleSheet.insertRule(
59
+ `${getSelector(blockTypeSelector)} { content: ${JSON.stringify(
60
+ placeholder,
61
+ )}; }`,
62
+ );
63
+ }
64
+
65
+ const onlyBlockSelector = `[data-is-only-empty-block]`;
66
+ const mustBeFocusedSelector = `[data-is-empty-and-focused]`;
67
+
68
+ // placeholder for when there's only one empty block
69
+ styleSheet.insertRule(
70
+ `${getSelector(onlyBlockSelector)} { content: ${JSON.stringify(
71
+ emptyPlaceholder,
72
+ )}; }`,
73
+ );
74
+
75
+ // placeholder for default blocks, only when the cursor is in the block (mustBeFocused)
76
+ styleSheet.insertRule(
77
+ `${getSelector(mustBeFocusedSelector)} { content: ${JSON.stringify(
78
+ defaultPlaceholder,
79
+ )}; }`,
80
+ );
81
+ } catch (e) {
82
+ // eslint-disable-next-line no-console
83
+ console.warn(
84
+ `Failed to insert placeholder CSS rule - this is likely due to the browser not supporting certain CSS pseudo-element selectors (:has, :only-child:, or :before)`,
85
+ e,
86
+ );
87
+ }
88
+
89
+ return {
90
+ destroy: () => {
91
+ if (view.root instanceof window.ShadowRoot) {
92
+ view.root.removeChild(styleEl);
93
+ } else {
94
+ view.root.head.removeChild(styleEl);
95
+ }
96
+ },
97
+ };
98
+ },
99
+ props: {
100
+ decorations: (state) => {
101
+ const { doc, selection } = state;
102
+
103
+ if (!editor.isEditable) {
104
+ return;
105
+ }
106
+
107
+ if (!selection.empty) {
108
+ return;
109
+ }
110
+
111
+ // Don't show placeholder when the cursor is inside a code block
112
+ if (selection.$from.parent.type.spec.code) {
113
+ return;
114
+ }
115
+
116
+ const decs = [];
117
+
118
+ // decoration for when there's only one empty block
119
+ // positions are hardcoded for now
120
+ if (state.doc.content.size === 6) {
121
+ decs.push(
122
+ Decoration.node(2, 4, {
123
+ "data-is-only-empty-block": "true",
124
+ }),
125
+ );
126
+ }
127
+
128
+ const $pos = selection.$anchor;
129
+ const node = $pos.parent;
130
+
131
+ if (node.content.size === 0) {
132
+ const before = $pos.before();
133
+
134
+ decs.push(
135
+ Decoration.node(before, before + node.nodeSize, {
136
+ "data-is-empty-and-focused": "true",
137
+ }),
138
+ );
139
+ }
140
+
141
+ return DecorationSet.create(doc, decs);
142
+ },
143
+ },
144
+ }),
145
+ ],
146
+ } as const;
147
+ },
148
+ );
@@ -1,7 +1,7 @@
1
1
  import { findChildren } from "@tiptap/core";
2
2
  import { Plugin, PluginKey } from "prosemirror-state";
3
3
  import { Decoration, DecorationSet } from "prosemirror-view";
4
- import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
4
+ import { createExtension } from "../../editor/BlockNoteExtension.js";
5
5
 
6
6
  const PLUGIN_KEY = new PluginKey(`previous-blocks`);
7
7
 
@@ -24,15 +24,11 @@ const nodeAttributes: Record<string, string> = {
24
24
  *
25
25
  * Solution: When attributes change on a node, this plugin sets a data-* attribute with the "previous" value. This way we can still use CSS transitions. (See block.module.css)
26
26
  */
27
- export class PreviousBlockTypePlugin extends BlockNoteExtension {
28
- public static key() {
29
- return "previousBlockType";
30
- }
31
-
32
- constructor() {
33
- super();
34
- let timeout: ReturnType<typeof setTimeout>;
35
- this.addProsemirrorPlugin(
27
+ export const PreviousBlockTypeExtension = createExtension(() => {
28
+ let timeout: ReturnType<typeof setTimeout>;
29
+ return {
30
+ key: "previousBlockType",
31
+ prosemirrorPlugins: [
36
32
  new Plugin({
37
33
  key: PLUGIN_KEY,
38
34
  view(_editorView) {
@@ -207,6 +203,6 @@ export class PreviousBlockTypePlugin extends BlockNoteExtension {
207
203
  },
208
204
  },
209
205
  }),
210
- );
211
- }
212
- }
206
+ ],
207
+ } as const;
208
+ });
@@ -1,7 +1,9 @@
1
1
  import { Plugin, PluginKey } from "prosemirror-state";
2
2
  import { Decoration, DecorationSet } from "prosemirror-view";
3
- import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
4
- import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
3
+ import {
4
+ createExtension,
5
+ createStore,
6
+ } from "../../editor/BlockNoteExtension.js";
5
7
 
6
8
  const PLUGIN_KEY = new PluginKey(`blocknote-show-selection`);
7
9
 
@@ -10,48 +12,40 @@ const PLUGIN_KEY = new PluginKey(`blocknote-show-selection`);
10
12
  * This can be used to highlight the current selection in the UI even when the
11
13
  * text editor is not focused.
12
14
  */
13
- export class ShowSelectionPlugin extends BlockNoteExtension {
14
- public static key() {
15
- return "showSelection";
16
- }
17
-
18
- private enabled = false;
19
-
20
- public constructor(private readonly editor: BlockNoteEditor<any, any, any>) {
21
- super();
22
- this.addProsemirrorPlugin(
15
+ export const ShowSelectionExtension = createExtension(({ editor }) => {
16
+ const store = createStore(
17
+ { enabled: false },
18
+ {
19
+ onUpdate() {
20
+ editor.transact((tr) => tr.setMeta(PLUGIN_KEY, {}));
21
+ },
22
+ },
23
+ );
24
+ return {
25
+ key: "showSelection",
26
+ store,
27
+ prosemirrorPlugins: [
23
28
  new Plugin({
24
29
  key: PLUGIN_KEY,
25
30
  props: {
26
31
  decorations: (state) => {
27
32
  const { doc, selection } = state;
28
-
29
- if (!this.enabled) {
33
+ if (!store.state.enabled) {
30
34
  return DecorationSet.empty;
31
35
  }
32
-
33
36
  const dec = Decoration.inline(selection.from, selection.to, {
34
37
  "data-show-selection": "true",
35
38
  });
36
-
37
39
  return DecorationSet.create(doc, [dec]);
38
40
  },
39
41
  },
40
42
  }),
41
- );
42
- }
43
-
44
- public setEnabled(enabled: boolean) {
45
- if (this.enabled === enabled) {
46
- return;
47
- }
48
-
49
- this.enabled = enabled;
50
-
51
- this.editor.transact((tr) => tr.setMeta(PLUGIN_KEY, {}));
52
- }
53
-
54
- public getEnabled() {
55
- return this.enabled;
56
- }
57
- }
43
+ ],
44
+ /**
45
+ * Show or hide the selection decoration
46
+ */
47
+ showSelection(shouldShow: boolean) {
48
+ store.setState({ enabled: shouldShow });
49
+ },
50
+ } as const;
51
+ });
@@ -10,7 +10,10 @@ import { EditorView } from "@tiptap/pm/view";
10
10
 
11
11
  import { Block } from "../../blocks/defaultBlocks.js";
12
12
  import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
13
- import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
13
+ import {
14
+ createExtension,
15
+ createStore,
16
+ } from "../../editor/BlockNoteExtension.js";
14
17
  import { UiElementPosition } from "../../extensions-shared/UiElementPosition.js";
15
18
  import {
16
19
  BlockSchema,
@@ -184,11 +187,6 @@ export class SideMenuView<
184
187
  this.onKeyDown as EventListener,
185
188
  true,
186
189
  );
187
-
188
- // Setting capture=true ensures that any parent container of the editor that
189
- // gets scrolled will trigger the scroll event. Scroll events do not bubble
190
- // and so won't propagate to the document by default.
191
- pmView.root.addEventListener("scroll", this.onScroll, true);
192
190
  }
193
191
 
194
192
  updateState = (state: SideMenuState<BSchema, I, S>) => {
@@ -621,14 +619,6 @@ export class SideMenuView<
621
619
  this.pmView.dom.dispatchEvent(evt);
622
620
  }
623
621
 
624
- onScroll = () => {
625
- if (this.state?.show) {
626
- this.state.referencePos = this.hoveredBlock!.getBoundingClientRect();
627
- this.emitUpdate(this.state);
628
- }
629
- this.updateStateFromMousePos();
630
- };
631
-
632
622
  // Needed in cases where the editor state updates without the mouse cursor
633
623
  // moving, as some state updates can require a side menu update. For example,
634
624
  // adding a button to the side menu which removes the block can cause the
@@ -676,89 +666,79 @@ export class SideMenuView<
676
666
  this.onKeyDown as EventListener,
677
667
  true,
678
668
  );
679
- this.pmView.root.removeEventListener("scroll", this.onScroll, true);
680
669
  }
681
670
  }
682
671
 
683
672
  export const sideMenuPluginKey = new PluginKey("SideMenuPlugin");
684
673
 
685
- export class SideMenuProsemirrorPlugin<
686
- BSchema extends BlockSchema,
687
- I extends InlineContentSchema,
688
- S extends StyleSchema,
689
- > extends BlockNoteExtension {
690
- public static key() {
691
- return "sideMenu";
692
- }
693
-
694
- public view: SideMenuView<BSchema, I, S> | undefined;
674
+ export const SideMenuExtension = createExtension(({ editor }) => {
675
+ let view: SideMenuView<any, any, any> | undefined;
676
+ const store = createStore<SideMenuState<any, any, any> | undefined>(
677
+ undefined,
678
+ );
695
679
 
696
- constructor(private readonly editor: BlockNoteEditor<BSchema, I, S>) {
697
- super();
698
- this.addProsemirrorPlugin(
680
+ return {
681
+ key: "sideMenu",
682
+ store,
683
+ prosemirrorPlugins: [
699
684
  new Plugin({
700
685
  key: sideMenuPluginKey,
701
686
  view: (editorView) => {
702
- this.view = new SideMenuView(editor, editorView, (state) => {
703
- this.emit("update", state);
687
+ view = new SideMenuView(editor, editorView, (state) => {
688
+ // TODO: Without spreading the state, in some cases like toggling
689
+ // `show`, this doesn't trigger an update.
690
+ store.setState({ ...state });
704
691
  });
705
- return this.view;
692
+ return view;
706
693
  },
707
694
  }),
708
- );
709
- }
710
-
711
- public onUpdate(callback: (state: SideMenuState<BSchema, I, S>) => void) {
712
- return this.on("update", callback);
713
- }
714
-
715
- /**
716
- * Handles drag & drop events for blocks.
717
- */
718
- blockDragStart = (
719
- event: {
720
- dataTransfer: DataTransfer | null;
721
- clientY: number;
695
+ ],
696
+
697
+ /**
698
+ * Handles drag & drop events for blocks.
699
+ */
700
+ blockDragStart(
701
+ event: { dataTransfer: DataTransfer | null; clientY: number },
702
+ block: Block<any, any, any>,
703
+ ) {
704
+ if (view) {
705
+ view.isDragOrigin = true;
706
+ }
707
+ dragStart(event, block, editor);
722
708
  },
723
- block: Block<BSchema, I, S>,
724
- ) => {
725
- if (this.view) {
726
- this.view.isDragOrigin = true;
727
- }
728
709
 
729
- dragStart(event, block, this.editor);
730
- };
710
+ /**
711
+ * Handles drag & drop events for blocks.
712
+ */
713
+ blockDragEnd() {
714
+ unsetDragImage(editor.prosemirrorView.root);
715
+ if (view) {
716
+ view.isDragOrigin = false;
717
+ }
731
718
 
732
- /**
733
- * Handles drag & drop events for blocks.
734
- */
735
- blockDragEnd = () => {
736
- unsetDragImage(this.editor.prosemirrorView.root);
719
+ editor.blur();
720
+ },
737
721
 
738
- if (this.view) {
739
- this.view.isDragOrigin = false;
740
- }
722
+ /**
723
+ * Freezes the side menu. When frozen, the side menu will stay
724
+ * attached to the same block regardless of which block is hovered by the
725
+ * mouse cursor.
726
+ */
727
+ freezeMenu() {
728
+ view!.menuFrozen = true;
729
+ view!.state!.show = true;
730
+ view!.emitUpdate(view!.state!);
731
+ },
741
732
 
742
- this.editor.blur();
743
- };
744
- /**
745
- * Freezes the side menu. When frozen, the side menu will stay
746
- * attached to the same block regardless of which block is hovered by the
747
- * mouse cursor.
748
- */
749
- freezeMenu = () => {
750
- this.view!.menuFrozen = true;
751
- this.view!.state!.show = true;
752
- this.view!.emitUpdate(this.view!.state!);
753
- };
754
- /**
755
- * Unfreezes the side menu. When frozen, the side menu will stay
756
- * attached to the same block regardless of which block is hovered by the
757
- * mouse cursor.
758
- */
759
- unfreezeMenu = () => {
760
- this.view!.menuFrozen = false;
761
- this.view!.state!.show = false;
762
- this.view!.emitUpdate(this.view!.state!);
763
- };
764
- }
733
+ /**
734
+ * Unfreezes the side menu. When frozen, the side menu will stay
735
+ * attached to the same block regardless of which block is hovered by the
736
+ * mouse cursor.
737
+ */
738
+ unfreezeMenu() {
739
+ view!.menuFrozen = false;
740
+ view!.state!.show = false;
741
+ view!.emitUpdate(view!.state!);
742
+ },
743
+ } as const;
744
+ });