@blocknote/core 0.19.2 → 0.21.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 (220) hide show
  1. package/dist/blocknote.js +2035 -1758
  2. package/dist/blocknote.js.map +1 -1
  3. package/dist/blocknote.umd.cjs +6 -6
  4. package/dist/blocknote.umd.cjs.map +1 -1
  5. package/dist/src/api/blockManipulation/commands/insertBlocks/insertBlocks.js +6 -3
  6. package/dist/src/api/blockManipulation/commands/insertBlocks/insertBlocks.js.map +1 -1
  7. package/dist/src/api/blockManipulation/commands/moveBlocks/moveBlocks.js +219 -0
  8. package/dist/src/api/blockManipulation/commands/moveBlocks/moveBlocks.js.map +1 -0
  9. package/dist/src/api/blockManipulation/commands/moveBlocks/moveBlocks.test.js +175 -0
  10. package/dist/src/api/blockManipulation/commands/moveBlocks/moveBlocks.test.js.map +1 -0
  11. package/dist/src/api/blockManipulation/commands/splitBlock/splitBlock.test.js +4 -1
  12. package/dist/src/api/blockManipulation/commands/splitBlock/splitBlock.test.js.map +1 -1
  13. package/dist/src/api/blockManipulation/commands/updateBlock/updateBlock.js +6 -3
  14. package/dist/src/api/blockManipulation/commands/updateBlock/updateBlock.js.map +1 -1
  15. package/dist/src/api/blockManipulation/getBlock/getBlock.js +56 -0
  16. package/dist/src/api/blockManipulation/getBlock/getBlock.js.map +1 -0
  17. package/dist/src/api/blockManipulation/selections/selection.js +149 -0
  18. package/dist/src/api/blockManipulation/selections/selection.js.map +1 -0
  19. package/dist/src/api/blockManipulation/selections/selection.test.js +39 -0
  20. package/dist/src/api/blockManipulation/selections/selection.test.js.map +1 -0
  21. package/dist/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.js +3 -0
  22. package/dist/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.js.map +1 -1
  23. package/dist/src/api/clipboard/clipboard.test.js +6 -3
  24. package/dist/src/api/clipboard/clipboard.test.js.map +1 -1
  25. package/dist/src/api/clipboard/fromClipboard/handleFileInsertion.js +1 -1
  26. package/dist/src/api/clipboard/fromClipboard/handleFileInsertion.js.map +1 -1
  27. package/dist/src/api/clipboard/fromClipboard/handleVSCodePaste.js +2 -3
  28. package/dist/src/api/clipboard/fromClipboard/handleVSCodePaste.js.map +1 -1
  29. package/dist/src/api/clipboard/fromClipboard/pasteExtension.js +5 -5
  30. package/dist/src/api/clipboard/fromClipboard/pasteExtension.js.map +1 -1
  31. package/dist/src/api/clipboard/toClipboard/copyExtension.js +4 -2
  32. package/dist/src/api/clipboard/toClipboard/copyExtension.js.map +1 -1
  33. package/dist/src/api/nodeUtil.js +1 -1
  34. package/dist/src/api/nodeUtil.js.map +1 -1
  35. package/dist/src/api/parsers/markdown/parseMarkdown.test.js +4 -1
  36. package/dist/src/api/parsers/markdown/parseMarkdown.test.js.map +1 -1
  37. package/dist/src/blocks/AudioBlockContent/AudioBlockContent.js +14 -7
  38. package/dist/src/blocks/AudioBlockContent/AudioBlockContent.js.map +1 -1
  39. package/dist/src/blocks/AudioBlockContent/{audioBlockHelpers.js → parseAudioElement.js} +1 -1
  40. package/dist/src/blocks/AudioBlockContent/parseAudioElement.js.map +1 -0
  41. package/dist/src/blocks/FileBlockContent/FileBlockContent.js +5 -4
  42. package/dist/src/blocks/FileBlockContent/FileBlockContent.js.map +1 -1
  43. package/dist/src/blocks/FileBlockContent/helpers/parse/parseEmbedElement.js +5 -0
  44. package/dist/src/blocks/FileBlockContent/helpers/parse/parseEmbedElement.js.map +1 -0
  45. package/dist/src/blocks/FileBlockContent/helpers/parse/parseFigureElement.js +10 -0
  46. package/dist/src/blocks/FileBlockContent/helpers/parse/parseFigureElement.js.map +1 -0
  47. package/dist/src/blocks/FileBlockContent/helpers/render/createAddFileButton.js +39 -0
  48. package/dist/src/blocks/FileBlockContent/helpers/render/createAddFileButton.js.map +1 -0
  49. package/dist/src/blocks/FileBlockContent/helpers/render/createFileBlockWrapper.js +51 -0
  50. package/dist/src/blocks/FileBlockContent/helpers/render/createFileBlockWrapper.js.map +1 -0
  51. package/dist/src/blocks/FileBlockContent/helpers/render/createFileNameWithIcon.js +17 -0
  52. package/dist/src/blocks/FileBlockContent/helpers/render/createFileNameWithIcon.js.map +1 -0
  53. package/dist/src/blocks/FileBlockContent/helpers/render/createResizableFileBlockWrapper.js +147 -0
  54. package/dist/src/blocks/FileBlockContent/helpers/render/createResizableFileBlockWrapper.js.map +1 -0
  55. package/dist/src/blocks/FileBlockContent/helpers/toExternalHTML/createFigureWithCaption.js +9 -0
  56. package/dist/src/blocks/FileBlockContent/helpers/toExternalHTML/createFigureWithCaption.js.map +1 -0
  57. package/dist/src/blocks/FileBlockContent/helpers/toExternalHTML/createLinkWithCaption.js +11 -0
  58. package/dist/src/blocks/FileBlockContent/helpers/toExternalHTML/createLinkWithCaption.js.map +1 -0
  59. package/dist/src/blocks/ImageBlockContent/ImageBlockContent.js +17 -9
  60. package/dist/src/blocks/ImageBlockContent/ImageBlockContent.js.map +1 -1
  61. package/dist/src/blocks/ImageBlockContent/{imageBlockHelpers.js → parseImageElement.js} +1 -1
  62. package/dist/src/blocks/ImageBlockContent/parseImageElement.js.map +1 -0
  63. package/dist/src/blocks/TableBlockContent/TableExtension.js +8 -1
  64. package/dist/src/blocks/TableBlockContent/TableExtension.js.map +1 -1
  65. package/dist/src/blocks/VideoBlockContent/VideoBlockContent.js +18 -7
  66. package/dist/src/blocks/VideoBlockContent/VideoBlockContent.js.map +1 -1
  67. package/dist/src/blocks/VideoBlockContent/{videoBlockHelpers.js → parseVideoElement.js} +1 -1
  68. package/dist/src/blocks/VideoBlockContent/parseVideoElement.js.map +1 -0
  69. package/dist/src/editor/BlockNoteEditor.js +64 -62
  70. package/dist/src/editor/BlockNoteEditor.js.map +1 -1
  71. package/dist/src/editor/BlockNoteExtensions.js +5 -8
  72. package/dist/src/editor/BlockNoteExtensions.js.map +1 -1
  73. package/dist/src/extensions/FormattingToolbar/FormattingToolbarPlugin.js +4 -2
  74. package/dist/src/extensions/FormattingToolbar/FormattingToolbarPlugin.js.map +1 -1
  75. package/dist/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.js +10 -8
  76. package/dist/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.js.map +1 -1
  77. package/dist/src/extensions/LinkToolbar/LinkToolbarPlugin.js +7 -3
  78. package/dist/src/extensions/LinkToolbar/LinkToolbarPlugin.js.map +1 -1
  79. package/dist/src/extensions/LinkToolbar/protocols.js +14 -0
  80. package/dist/src/extensions/LinkToolbar/protocols.js.map +1 -0
  81. package/dist/src/extensions/Placeholder/PlaceholderPlugin.js +19 -13
  82. package/dist/src/extensions/Placeholder/PlaceholderPlugin.js.map +1 -1
  83. package/dist/src/extensions/SideMenu/SideMenuPlugin.js +5 -1
  84. package/dist/src/extensions/SideMenu/SideMenuPlugin.js.map +1 -1
  85. package/dist/src/extensions/SideMenu/dragging.js +8 -1
  86. package/dist/src/extensions/SideMenu/dragging.js.map +1 -1
  87. package/dist/src/extensions/SuggestionMenu/SuggestionPlugin.js +3 -3
  88. package/dist/src/extensions/SuggestionMenu/SuggestionPlugin.js.map +1 -1
  89. package/dist/src/extensions/TableHandles/TableHandlesPlugin.js +37 -11
  90. package/dist/src/extensions/TableHandles/TableHandlesPlugin.js.map +1 -1
  91. package/dist/src/i18n/locales/ru.js +1 -1
  92. package/dist/src/index.js +9 -2
  93. package/dist/src/index.js.map +1 -1
  94. package/dist/src/schema/inlineContent/createSpec.js +1 -1
  95. package/dist/src/schema/inlineContent/createSpec.js.map +1 -1
  96. package/dist/style.css +1 -1
  97. package/dist/tsconfig.tsbuildinfo +1 -1
  98. package/dist/webpack-stats.json +1 -1
  99. package/package.json +3 -3
  100. package/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts +6 -6
  101. package/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap +9506 -0
  102. package/src/api/blockManipulation/commands/moveBlocks/moveBlocks.test.ts +295 -0
  103. package/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts +336 -0
  104. package/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts +5 -1
  105. package/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +11 -3
  106. package/src/api/blockManipulation/getBlock/getBlock.ts +141 -0
  107. package/src/api/blockManipulation/selections/__snapshots__/selection.test.ts.snap +660 -0
  108. package/src/api/blockManipulation/selections/selection.test.ts +56 -0
  109. package/src/api/blockManipulation/selections/selection.ts +244 -0
  110. package/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts +4 -0
  111. package/src/api/clipboard/__snapshots__/tableAllCells.html +1 -1
  112. package/src/api/clipboard/__snapshots__/tableCell.html +1 -1
  113. package/src/api/clipboard/__snapshots__/tableRow.html +1 -1
  114. package/src/api/clipboard/clipboard.test.ts +7 -3
  115. package/src/api/clipboard/fromClipboard/handleFileInsertion.ts +1 -1
  116. package/src/api/clipboard/fromClipboard/handleVSCodePaste.ts +7 -14
  117. package/src/api/clipboard/fromClipboard/pasteExtension.ts +6 -6
  118. package/src/api/clipboard/toClipboard/copyExtension.ts +7 -2
  119. package/src/api/exporters/html/__snapshots__/file/basic/internal.html +1 -1
  120. package/src/api/exporters/html/__snapshots__/file/nested/internal.html +1 -1
  121. package/src/api/exporters/html/__snapshots__/file/noCaption/internal.html +1 -1
  122. package/src/api/exporters/html/__snapshots__/file/noName/internal.html +1 -1
  123. package/src/api/exporters/html/__snapshots__/image/basic/internal.html +1 -1
  124. package/src/api/exporters/html/__snapshots__/image/nested/internal.html +1 -1
  125. package/src/api/exporters/html/__snapshots__/image/noCaption/internal.html +1 -1
  126. package/src/api/exporters/html/__snapshots__/image/noName/internal.html +1 -1
  127. package/src/api/exporters/html/__snapshots__/image/noPreview/internal.html +1 -1
  128. package/src/api/exporters/html/__snapshots__/simpleImage/basic/external.html +1 -1
  129. package/src/api/exporters/html/__snapshots__/simpleImage/basic/internal.html +1 -1
  130. package/src/api/exporters/html/__snapshots__/simpleImage/nested/external.html +1 -1
  131. package/src/api/exporters/html/__snapshots__/simpleImage/nested/internal.html +1 -1
  132. package/src/api/exporters/html/__snapshots__/simpleImage/noCaption/external.html +1 -1
  133. package/src/api/exporters/html/__snapshots__/simpleImage/noCaption/internal.html +1 -1
  134. package/src/api/exporters/html/__snapshots__/simpleImage/noName/external.html +1 -1
  135. package/src/api/exporters/html/__snapshots__/simpleImage/noName/internal.html +1 -1
  136. package/src/api/exporters/html/__snapshots__/simpleImage/noPreview/external.html +1 -1
  137. package/src/api/exporters/html/__snapshots__/simpleImage/noPreview/internal.html +1 -1
  138. package/src/api/exporters/markdown/__snapshots__/simpleImage/basic/markdown.md +1 -1
  139. package/src/api/exporters/markdown/__snapshots__/simpleImage/nested/markdown.md +2 -2
  140. package/src/api/exporters/markdown/__snapshots__/simpleImage/noCaption/markdown.md +1 -1
  141. package/src/api/exporters/markdown/__snapshots__/simpleImage/noName/markdown.md +1 -1
  142. package/src/api/nodeUtil.ts +2 -2
  143. package/src/api/parsers/markdown/parseMarkdown.test.ts +5 -7
  144. package/src/blocks/AudioBlockContent/AudioBlockContent.ts +13 -14
  145. package/src/blocks/FileBlockContent/FileBlockContent.ts +5 -12
  146. package/src/blocks/FileBlockContent/helpers/parse/parseEmbedElement.ts +5 -0
  147. package/src/blocks/FileBlockContent/helpers/parse/parseFigureElement.ts +16 -0
  148. package/src/blocks/FileBlockContent/helpers/render/createAddFileButton.ts +63 -0
  149. package/src/blocks/FileBlockContent/helpers/render/createFileBlockWrapper.ts +80 -0
  150. package/src/blocks/FileBlockContent/helpers/render/createFileNameWithIcon.ts +24 -0
  151. package/src/blocks/FileBlockContent/helpers/render/createResizableFileBlockWrapper.ts +204 -0
  152. package/src/blocks/FileBlockContent/helpers/toExternalHTML/createFigureWithCaption.ts +13 -0
  153. package/src/blocks/FileBlockContent/helpers/toExternalHTML/createLinkWithCaption.ts +15 -0
  154. package/src/blocks/ImageBlockContent/ImageBlockContent.ts +20 -28
  155. package/src/blocks/TableBlockContent/TableExtension.ts +12 -1
  156. package/src/blocks/VideoBlockContent/VideoBlockContent.ts +20 -27
  157. package/src/editor/Block.css +35 -51
  158. package/src/editor/BlockNoteEditor.ts +101 -92
  159. package/src/editor/BlockNoteExtensions.ts +9 -8
  160. package/src/editor/editor.css +1 -0
  161. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +4 -2
  162. package/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +11 -8
  163. package/src/extensions/LinkToolbar/LinkToolbarPlugin.ts +11 -4
  164. package/src/extensions/LinkToolbar/protocols.ts +13 -0
  165. package/src/extensions/Placeholder/PlaceholderPlugin.ts +29 -21
  166. package/src/extensions/SideMenu/SideMenuPlugin.ts +5 -1
  167. package/src/extensions/SideMenu/dragging.ts +8 -1
  168. package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +3 -6
  169. package/src/extensions/TableHandles/TableHandlesPlugin.ts +49 -12
  170. package/src/i18n/locales/ru.ts +1 -1
  171. package/src/index.ts +9 -2
  172. package/src/schema/inlineContent/createSpec.ts +2 -2
  173. package/types/src/api/blockManipulation/commands/moveBlocks/moveBlocks.d.ts +15 -0
  174. package/types/src/api/blockManipulation/getBlock/getBlock.d.ts +7 -0
  175. package/types/src/api/blockManipulation/selections/selection.d.ts +5 -0
  176. package/types/src/api/blockManipulation/selections/selection.test.d.ts +1 -0
  177. package/types/src/api/clipboard/fromClipboard/handleVSCodePaste.d.ts +2 -3
  178. package/types/src/api/clipboard/fromClipboard/pasteExtension.d.ts +1 -3
  179. package/types/src/api/nodeUtil.d.ts +1 -1
  180. package/types/src/blocks/AudioBlockContent/AudioBlockContent.d.ts +2 -5
  181. package/types/src/blocks/FileBlockContent/FileBlockContent.d.ts +2 -5
  182. package/types/src/blocks/FileBlockContent/helpers/parse/parseEmbedElement.d.ts +3 -0
  183. package/types/src/blocks/FileBlockContent/helpers/parse/parseFigureElement.d.ts +4 -0
  184. package/types/src/blocks/FileBlockContent/helpers/render/createAddFileButton.d.ts +6 -0
  185. package/types/src/blocks/FileBlockContent/helpers/render/createFileBlockWrapper.d.ts +9 -0
  186. package/types/src/blocks/FileBlockContent/helpers/render/createFileNameWithIcon.d.ts +6 -0
  187. package/types/src/blocks/FileBlockContent/helpers/render/createResizableFileBlockWrapper.d.ts +9 -0
  188. package/types/src/blocks/FileBlockContent/helpers/toExternalHTML/createFigureWithCaption.d.ts +3 -0
  189. package/types/src/blocks/FileBlockContent/helpers/toExternalHTML/createLinkWithCaption.d.ts +3 -0
  190. package/types/src/blocks/ImageBlockContent/ImageBlockContent.d.ts +2 -5
  191. package/types/src/blocks/VideoBlockContent/VideoBlockContent.d.ts +2 -5
  192. package/types/src/editor/BlockNoteEditor.d.ts +60 -14
  193. package/types/src/editor/BlockNoteExtensions.d.ts +1 -0
  194. package/types/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.d.ts +1 -0
  195. package/types/src/extensions/LinkToolbar/protocols.d.ts +2 -0
  196. package/types/src/index.d.ts +9 -2
  197. package/types/src/pm-nodes/BlockContainer.d.ts +2 -2
  198. package/types/src/pm-nodes/BlockGroup.d.ts +2 -2
  199. package/dist/src/api/blockManipulation/commands/moveBlock/moveBlock.js +0 -116
  200. package/dist/src/api/blockManipulation/commands/moveBlock/moveBlock.js.map +0 -1
  201. package/dist/src/api/blockManipulation/commands/moveBlock/moveBlock.test.js +0 -110
  202. package/dist/src/api/blockManipulation/commands/moveBlock/moveBlock.test.js.map +0 -1
  203. package/dist/src/blocks/AudioBlockContent/audioBlockHelpers.js.map +0 -1
  204. package/dist/src/blocks/FileBlockContent/fileBlockHelpers.js +0 -317
  205. package/dist/src/blocks/FileBlockContent/fileBlockHelpers.js.map +0 -1
  206. package/dist/src/blocks/ImageBlockContent/imageBlockHelpers.js.map +0 -1
  207. package/dist/src/blocks/VideoBlockContent/videoBlockHelpers.js.map +0 -1
  208. package/src/api/blockManipulation/commands/moveBlock/__snapshots__/moveBlock.test.ts.snap +0 -3799
  209. package/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts +0 -196
  210. package/src/api/blockManipulation/commands/moveBlock/moveBlock.ts +0 -176
  211. package/src/blocks/FileBlockContent/fileBlockHelpers.ts +0 -456
  212. package/types/src/api/blockManipulation/commands/moveBlock/moveBlock.d.ts +0 -5
  213. package/types/src/blocks/FileBlockContent/fileBlockHelpers.d.ts +0 -41
  214. /package/src/blocks/AudioBlockContent/{audioBlockHelpers.ts → parseAudioElement.ts} +0 -0
  215. /package/src/blocks/ImageBlockContent/{imageBlockHelpers.ts → parseImageElement.ts} +0 -0
  216. /package/src/blocks/VideoBlockContent/{videoBlockHelpers.ts → parseVideoElement.ts} +0 -0
  217. /package/types/src/api/blockManipulation/commands/{moveBlock/moveBlock.test.d.ts → moveBlocks/moveBlocks.test.d.ts} +0 -0
  218. /package/types/src/blocks/AudioBlockContent/{audioBlockHelpers.d.ts → parseAudioElement.d.ts} +0 -0
  219. /package/types/src/blocks/ImageBlockContent/{imageBlockHelpers.d.ts → parseImageElement.d.ts} +0 -0
  220. /package/types/src/blocks/VideoBlockContent/{videoBlockHelpers.d.ts → parseVideoElement.d.ts} +0 -0
@@ -0,0 +1,295 @@
1
+ import { NodeSelection, TextSelection } from "prosemirror-state";
2
+ import { CellSelection } from "prosemirror-tables";
3
+ import { describe, expect, it } from "vitest";
4
+
5
+ import { getBlockInfoFromSelection } from "../../../getBlockInfoFromPos.js";
6
+ import { setupTestEnv } from "../../setupTestEnv.js";
7
+ import {
8
+ moveBlocksDown,
9
+ moveBlocksUp,
10
+ moveSelectedBlocksAndSelection,
11
+ } from "./moveBlocks.js";
12
+
13
+ const getEditor = setupTestEnv();
14
+
15
+ function makeSelectionSpanContent(selectionType: "text" | "node" | "cell") {
16
+ const blockInfo = getBlockInfoFromSelection(getEditor()._tiptapEditor.state);
17
+ if (!blockInfo.isBlockContainer) {
18
+ throw new Error(
19
+ `Selection points to a ${blockInfo.blockNoteType} node, not a blockContainer node`
20
+ );
21
+ }
22
+ const { blockContent } = blockInfo;
23
+
24
+ if (selectionType === "cell") {
25
+ getEditor().dispatch(
26
+ getEditor()._tiptapEditor.state.tr.setSelection(
27
+ CellSelection.create(
28
+ getEditor()._tiptapEditor.state.doc,
29
+ getEditor()
30
+ ._tiptapEditor.state.doc.resolve(blockContent.beforePos + 3)
31
+ .before(),
32
+ getEditor()
33
+ ._tiptapEditor.state.doc.resolve(blockContent.afterPos - 3)
34
+ .before()
35
+ )
36
+ )
37
+ );
38
+ } else if (selectionType === "node") {
39
+ getEditor().dispatch(
40
+ getEditor()._tiptapEditor.state.tr.setSelection(
41
+ NodeSelection.create(
42
+ getEditor()._tiptapEditor.state.doc,
43
+ blockContent.beforePos
44
+ )
45
+ )
46
+ );
47
+ } else {
48
+ getEditor().dispatch(
49
+ getEditor()._tiptapEditor.state.tr.setSelection(
50
+ TextSelection.create(
51
+ getEditor()._tiptapEditor.state.doc,
52
+ blockContent.beforePos + 1,
53
+ blockContent.afterPos - 1
54
+ )
55
+ )
56
+ );
57
+ }
58
+ }
59
+
60
+ describe("Test moveSelectedBlockAndSelection", () => {
61
+ it("Text selection", () => {
62
+ getEditor().setTextCursorPosition("paragraph-1");
63
+ makeSelectionSpanContent("text");
64
+
65
+ moveSelectedBlocksAndSelection(getEditor(), "paragraph-0", "before");
66
+
67
+ const selection = getEditor()._tiptapEditor.state.selection;
68
+ getEditor().setTextCursorPosition("paragraph-1");
69
+ makeSelectionSpanContent("text");
70
+
71
+ expect(
72
+ selection.eq(getEditor()._tiptapEditor.state.selection)
73
+ ).toBeTruthy();
74
+ });
75
+
76
+ it("Node selection", () => {
77
+ getEditor().setTextCursorPosition("image-0");
78
+ makeSelectionSpanContent("node");
79
+
80
+ moveSelectedBlocksAndSelection(getEditor(), "paragraph-0", "before");
81
+
82
+ const selection = getEditor()._tiptapEditor.state.selection;
83
+ getEditor().setTextCursorPosition("image-0");
84
+ makeSelectionSpanContent("node");
85
+
86
+ expect(
87
+ selection.eq(getEditor()._tiptapEditor.state.selection)
88
+ ).toBeTruthy();
89
+ });
90
+
91
+ it("Cell selection", () => {
92
+ getEditor().setTextCursorPosition("table-0");
93
+ makeSelectionSpanContent("cell");
94
+
95
+ moveSelectedBlocksAndSelection(getEditor(), "paragraph-0", "before");
96
+
97
+ const selection = getEditor()._tiptapEditor.state.selection;
98
+ getEditor().setTextCursorPosition("table-0");
99
+ makeSelectionSpanContent("cell");
100
+
101
+ expect(
102
+ selection.eq(getEditor()._tiptapEditor.state.selection)
103
+ ).toBeTruthy();
104
+ });
105
+
106
+ it("Multiple block selection", () => {
107
+ getEditor().setSelection("paragraph-1", "paragraph-2");
108
+
109
+ moveSelectedBlocksAndSelection(getEditor(), "paragraph-0", "before");
110
+
111
+ const selection = getEditor()._tiptapEditor.state.selection;
112
+ getEditor().setSelection("paragraph-1", "paragraph-2");
113
+
114
+ expect(
115
+ selection.eq(getEditor()._tiptapEditor.state.selection)
116
+ ).toBeTruthy();
117
+ });
118
+
119
+ it("Multiple block selection with table", () => {
120
+ getEditor().setSelection("paragraph-6", "table-0");
121
+
122
+ moveSelectedBlocksAndSelection(getEditor(), "paragraph-0", "before");
123
+
124
+ const selection = getEditor()._tiptapEditor.state.selection;
125
+ getEditor().setSelection("paragraph-6", "table-0");
126
+
127
+ expect(
128
+ selection.eq(getEditor()._tiptapEditor.state.selection)
129
+ ).toBeTruthy();
130
+ });
131
+ });
132
+
133
+ describe("Test moveBlocksUp", () => {
134
+ it("Basic", () => {
135
+ getEditor().setTextCursorPosition("paragraph-1");
136
+
137
+ moveBlocksUp(getEditor());
138
+
139
+ expect(getEditor().document).toMatchSnapshot();
140
+ });
141
+
142
+ it("Into children", () => {
143
+ getEditor().setTextCursorPosition("paragraph-2");
144
+
145
+ moveBlocksUp(getEditor());
146
+
147
+ expect(getEditor().document).toMatchSnapshot();
148
+ });
149
+
150
+ it("Out of children", () => {
151
+ getEditor().setTextCursorPosition("nested-paragraph-1");
152
+
153
+ moveBlocksUp(getEditor());
154
+
155
+ expect(getEditor().document).toMatchSnapshot();
156
+ });
157
+
158
+ it("First block", () => {
159
+ getEditor().setTextCursorPosition("paragraph-0");
160
+
161
+ moveBlocksUp(getEditor());
162
+
163
+ expect(getEditor().document).toMatchSnapshot();
164
+ });
165
+
166
+ it("Multiple blocks", () => {
167
+ getEditor().setSelection("paragraph-1", "paragraph-2");
168
+
169
+ moveBlocksUp(getEditor());
170
+
171
+ expect(getEditor().document).toMatchSnapshot();
172
+ });
173
+
174
+ it("Multiple blocks starting in block with children", () => {
175
+ getEditor().setSelection("paragraph-with-children", "paragraph-2");
176
+
177
+ moveBlocksUp(getEditor());
178
+
179
+ expect(getEditor().document).toMatchSnapshot();
180
+ });
181
+
182
+ it("Multiple blocks starting in nested block", () => {
183
+ getEditor().setSelection("nested-paragraph-0", "paragraph-2");
184
+
185
+ moveBlocksUp(getEditor());
186
+
187
+ expect(getEditor().document).toMatchSnapshot();
188
+ });
189
+
190
+ it("Multiple blocks ending in block with children", () => {
191
+ getEditor().setSelection("paragraph-1", "paragraph-with-children");
192
+
193
+ moveBlocksUp(getEditor());
194
+
195
+ expect(getEditor().document).toMatchSnapshot();
196
+ });
197
+
198
+ it("Multiple blocks ending in nested block", () => {
199
+ getEditor().setSelection("paragraph-1", "nested-paragraph-0");
200
+
201
+ moveBlocksUp(getEditor());
202
+
203
+ expect(getEditor().document).toMatchSnapshot();
204
+ });
205
+
206
+ it("Multiple blocks starting and ending in nested block", () => {
207
+ getEditor().setSelection("nested-paragraph-0", "nested-paragraph-1");
208
+
209
+ moveBlocksUp(getEditor());
210
+
211
+ expect(getEditor().document).toMatchSnapshot();
212
+ });
213
+ });
214
+
215
+ describe("Test moveBlocksDown", () => {
216
+ it("Basic", () => {
217
+ getEditor().setTextCursorPosition("paragraph-0");
218
+
219
+ moveBlocksDown(getEditor());
220
+
221
+ expect(getEditor().document).toMatchSnapshot();
222
+ });
223
+
224
+ it("Into children", () => {
225
+ getEditor().setTextCursorPosition("paragraph-1");
226
+
227
+ moveBlocksDown(getEditor());
228
+
229
+ expect(getEditor().document).toMatchSnapshot();
230
+ });
231
+
232
+ it("Out of children", () => {
233
+ getEditor().setTextCursorPosition("nested-paragraph-1");
234
+
235
+ moveBlocksDown(getEditor());
236
+
237
+ expect(getEditor().document).toMatchSnapshot();
238
+ });
239
+
240
+ it("Last block", () => {
241
+ getEditor().setTextCursorPosition("trailing-paragraph");
242
+
243
+ moveBlocksDown(getEditor());
244
+
245
+ expect(getEditor().document).toMatchSnapshot();
246
+ });
247
+
248
+ it("Multiple blocks", () => {
249
+ getEditor().setSelection("paragraph-1", "paragraph-2");
250
+
251
+ moveBlocksDown(getEditor());
252
+
253
+ expect(getEditor().document).toMatchSnapshot();
254
+ });
255
+
256
+ it("Multiple blocks starting in block with children", () => {
257
+ getEditor().setSelection("paragraph-with-children", "paragraph-2");
258
+
259
+ moveBlocksDown(getEditor());
260
+
261
+ expect(getEditor().document).toMatchSnapshot();
262
+ });
263
+
264
+ it("Multiple blocks starting in nested block", () => {
265
+ getEditor().setSelection("nested-paragraph-0", "paragraph-2");
266
+
267
+ moveBlocksDown(getEditor());
268
+
269
+ expect(getEditor().document).toMatchSnapshot();
270
+ });
271
+
272
+ it("Multiple blocks ending in block with children", () => {
273
+ getEditor().setSelection("paragraph-1", "paragraph-with-children");
274
+
275
+ moveBlocksDown(getEditor());
276
+
277
+ expect(getEditor().document).toMatchSnapshot();
278
+ });
279
+
280
+ it("Multiple blocks ending in nested block", () => {
281
+ getEditor().setSelection("paragraph-1", "nested-paragraph-0");
282
+
283
+ moveBlocksDown(getEditor());
284
+
285
+ expect(getEditor().document).toMatchSnapshot();
286
+ });
287
+
288
+ it("Multiple blocks starting and ending in nested block", () => {
289
+ getEditor().setSelection("nested-paragraph-0", "nested-paragraph-1");
290
+
291
+ moveBlocksDown(getEditor());
292
+
293
+ expect(getEditor().document).toMatchSnapshot();
294
+ });
295
+ });
@@ -0,0 +1,336 @@
1
+ import { NodeSelection, Selection, TextSelection } from "prosemirror-state";
2
+ import { CellSelection } from "prosemirror-tables";
3
+
4
+ import { Block } from "../../../../blocks/defaultBlocks.js";
5
+ import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor";
6
+ import { BlockIdentifier } from "../../../../schema/index.js";
7
+ import { getNearestBlockPos } from "../../../getBlockInfoFromPos.js";
8
+ import { getNodeById } from "../../../nodeUtil.js";
9
+
10
+ type BlockSelectionData = (
11
+ | {
12
+ type: "text";
13
+ headBlockId: string;
14
+ anchorOffset: number;
15
+ headOffset: number;
16
+ }
17
+ | {
18
+ type: "node";
19
+ }
20
+ | {
21
+ type: "cell";
22
+ anchorCellOffset: number;
23
+ headCellOffset: number;
24
+ }
25
+ ) & {
26
+ anchorBlockId: string;
27
+ };
28
+
29
+ /**
30
+ * `getBlockSelectionData` and `updateBlockSelectionFromData` are used to save
31
+ * and restore the selection within a block, when the block is moved. This is
32
+ * done by first saving the offsets of the anchor and head from the before
33
+ * positions of their surrounding blocks, as well as the IDs of those blocks. We
34
+ * can then recreate the selection by finding the blocks with those IDs, getting
35
+ * their before positions, and adding the offsets to those positions.
36
+ * @param editor The BlockNote editor instance to get the selection data from.
37
+ */
38
+ function getBlockSelectionData(
39
+ editor: BlockNoteEditor<any, any, any>
40
+ ): BlockSelectionData {
41
+ const state = editor._tiptapEditor.state;
42
+ const selection = state.selection;
43
+
44
+ const anchorBlockPosInfo = getNearestBlockPos(state.doc, selection.anchor);
45
+
46
+ if (selection instanceof CellSelection) {
47
+ return {
48
+ type: "cell" as const,
49
+ anchorBlockId: anchorBlockPosInfo.node.attrs.id,
50
+ anchorCellOffset:
51
+ selection.$anchorCell.pos - anchorBlockPosInfo.posBeforeNode,
52
+ headCellOffset:
53
+ selection.$headCell.pos - anchorBlockPosInfo.posBeforeNode,
54
+ };
55
+ } else if (editor._tiptapEditor.state.selection instanceof NodeSelection) {
56
+ return {
57
+ type: "node" as const,
58
+ anchorBlockId: anchorBlockPosInfo.node.attrs.id,
59
+ };
60
+ } else {
61
+ const headBlockPosInfo = getNearestBlockPos(state.doc, selection.head);
62
+
63
+ return {
64
+ type: "text" as const,
65
+ anchorBlockId: anchorBlockPosInfo.node.attrs.id,
66
+ headBlockId: headBlockPosInfo.node.attrs.id,
67
+ anchorOffset: selection.anchor - anchorBlockPosInfo.posBeforeNode,
68
+ headOffset: selection.head - headBlockPosInfo.posBeforeNode,
69
+ };
70
+ }
71
+ }
72
+
73
+ /**
74
+ * `getBlockSelectionData` and `updateBlockSelectionFromData` are used to save
75
+ * and restore the selection within a block, when the block is moved. This is
76
+ * done by first saving the offsets of the anchor and head from the before
77
+ * positions of their surrounding blocks, as well as the IDs of those blocks. We
78
+ * can then recreate the selection by finding the blocks with those IDs, getting
79
+ * their before positions, and adding the offsets to those positions.
80
+ * @param editor The BlockNote editor instance to update the selection in.
81
+ * @param data The selection data to update the selection with (generated by
82
+ * `getBlockSelectionData`).
83
+ */
84
+ function updateBlockSelectionFromData(
85
+ editor: BlockNoteEditor<any, any, any>,
86
+ data: BlockSelectionData
87
+ ) {
88
+ const anchorBlockPos = getNodeById(
89
+ data.anchorBlockId,
90
+ editor._tiptapEditor.state.doc
91
+ )?.posBeforeNode;
92
+ if (anchorBlockPos === undefined) {
93
+ throw new Error(
94
+ `Could not find block with ID ${data.anchorBlockId} to update selection`
95
+ );
96
+ }
97
+
98
+ let selection: Selection;
99
+ if (data.type === "cell") {
100
+ selection = CellSelection.create(
101
+ editor._tiptapEditor.state.doc,
102
+ anchorBlockPos + data.anchorCellOffset,
103
+ anchorBlockPos + data.headCellOffset
104
+ );
105
+ } else if (data.type === "node") {
106
+ selection = NodeSelection.create(
107
+ editor._tiptapEditor.state.doc,
108
+ anchorBlockPos + 1
109
+ );
110
+ } else {
111
+ const headBlockPos = getNodeById(
112
+ data.headBlockId,
113
+ editor._tiptapEditor.state.doc
114
+ )?.posBeforeNode;
115
+ if (headBlockPos === undefined) {
116
+ throw new Error(
117
+ `Could not find block with ID ${data.headBlockId} to update selection`
118
+ );
119
+ }
120
+
121
+ selection = TextSelection.create(
122
+ editor._tiptapEditor.state.doc,
123
+ anchorBlockPos + data.anchorOffset,
124
+ headBlockPos + data.headOffset
125
+ );
126
+ }
127
+
128
+ editor.dispatch(editor._tiptapEditor.state.tr.setSelection(selection));
129
+ }
130
+
131
+ /**
132
+ * Replaces any `columnList` blocks with the children of their columns. This is
133
+ * done here instead of in `getSelection` as we still need to remove the entire
134
+ * `columnList` node but only insert the `blockContainer` nodes inside it.
135
+ * @param blocks The blocks to flatten.
136
+ */
137
+ function flattenColumns(
138
+ blocks: Block<any, any, any>[]
139
+ ): Block<any, any, any>[] {
140
+ return blocks
141
+ .map((block) => {
142
+ if (block.type === "columnList") {
143
+ return block.children
144
+ .map((column) => flattenColumns(column.children))
145
+ .flat();
146
+ }
147
+
148
+ return {
149
+ ...block,
150
+ children: flattenColumns(block.children),
151
+ };
152
+ })
153
+ .flat();
154
+ }
155
+
156
+ /**
157
+ * Removes the selected blocks from the editor, then inserts them before/after a
158
+ * reference block. Also updates the selection to match the original selection
159
+ * using `getBlockSelectionData` and `updateBlockSelectionFromData`.
160
+ * @param editor The BlockNote editor instance to move the blocks in.
161
+ * @param referenceBlock The reference block to insert the selected blocks
162
+ * before/after.
163
+ * @param placement Whether to insert the selected blocks before or after the
164
+ * reference block.
165
+ */
166
+ export function moveSelectedBlocksAndSelection(
167
+ editor: BlockNoteEditor<any, any, any>,
168
+ referenceBlock: BlockIdentifier,
169
+ placement: "before" | "after"
170
+ ) {
171
+ const blocks = editor.getSelection()?.blocks || [
172
+ editor.getTextCursorPosition().block,
173
+ ];
174
+ const selectionData = getBlockSelectionData(editor);
175
+
176
+ editor.removeBlocks(blocks);
177
+ editor.insertBlocks(flattenColumns(blocks), referenceBlock, placement);
178
+
179
+ updateBlockSelectionFromData(editor, selectionData);
180
+ }
181
+
182
+ // Checks if a block is in a valid place after being moved. This check is
183
+ // primitive at the moment and only returns false if the block's parent is a
184
+ // `columnList` block. This is because regular blocks cannot be direct children
185
+ // of `columnList` blocks.
186
+ function checkPlacementIsValid(parentBlock?: Block<any, any, any>): boolean {
187
+ return !parentBlock || parentBlock.type !== "columnList";
188
+ }
189
+
190
+ // Gets the placement for moving a block up. This has 3 cases:
191
+ // 1. If the block has a previous sibling without children, the placement is
192
+ // before it.
193
+ // 2. If the block has a previous sibling with children, the placement is after
194
+ // the last child.
195
+ // 3. If the block has no previous sibling, but is nested, the placement is
196
+ // before its parent.
197
+ // If the placement is invalid, the function is called recursively until a valid
198
+ // placement is found. Returns undefined if no valid placement is found, meaning
199
+ // the block is already at the top of the document.
200
+ function getMoveUpPlacement(
201
+ editor: BlockNoteEditor<any, any, any>,
202
+ prevBlock?: Block<any, any, any>,
203
+ parentBlock?: Block<any, any, any>
204
+ ):
205
+ | { referenceBlock: BlockIdentifier; placement: "before" | "after" }
206
+ | undefined {
207
+ let referenceBlock: Block<any, any, any> | undefined;
208
+ let placement: "before" | "after" | undefined;
209
+
210
+ if (!prevBlock) {
211
+ if (parentBlock) {
212
+ referenceBlock = parentBlock;
213
+ placement = "before";
214
+ }
215
+ } else if (prevBlock.children.length > 0) {
216
+ referenceBlock = prevBlock.children[prevBlock.children.length - 1];
217
+ placement = "after";
218
+ } else {
219
+ referenceBlock = prevBlock;
220
+ placement = "before";
221
+ }
222
+
223
+ // Case when the block is already at the top of the document.
224
+ if (!referenceBlock || !placement) {
225
+ return undefined;
226
+ }
227
+
228
+ const referenceBlockParent = editor.getParentBlock(referenceBlock);
229
+ if (!checkPlacementIsValid(referenceBlockParent)) {
230
+ return getMoveUpPlacement(
231
+ editor,
232
+ placement === "after"
233
+ ? referenceBlock
234
+ : editor.getPrevBlock(referenceBlock),
235
+ referenceBlockParent
236
+ );
237
+ }
238
+
239
+ return { referenceBlock, placement };
240
+ }
241
+
242
+ // Gets the placement for moving a block down. This has 3 cases:
243
+ // 1. If the block has a next sibling without children, the placement is after
244
+ // it.
245
+ // 2. If the block has a next sibling with children, the placement is before the
246
+ // first child.
247
+ // 3. If the block has no next sibling, but is nested, the placement is
248
+ // after its parent.
249
+ // If the placement is invalid, the function is called recursively until a valid
250
+ // placement is found. Returns undefined if no valid placement is found, meaning
251
+ // the block is already at the bottom of the document.
252
+ function getMoveDownPlacement(
253
+ editor: BlockNoteEditor<any, any, any>,
254
+ nextBlock?: Block<any, any, any>,
255
+ parentBlock?: Block<any, any, any>
256
+ ):
257
+ | { referenceBlock: BlockIdentifier; placement: "before" | "after" }
258
+ | undefined {
259
+ let referenceBlock: Block<any, any, any> | undefined;
260
+ let placement: "before" | "after" | undefined;
261
+
262
+ if (!nextBlock) {
263
+ if (parentBlock) {
264
+ referenceBlock = parentBlock;
265
+ placement = "after";
266
+ }
267
+ } else if (nextBlock.children.length > 0) {
268
+ referenceBlock = nextBlock.children[0];
269
+ placement = "before";
270
+ } else {
271
+ referenceBlock = nextBlock;
272
+ placement = "after";
273
+ }
274
+
275
+ // Case when the block is already at the bottom of the document.
276
+ if (!referenceBlock || !placement) {
277
+ return undefined;
278
+ }
279
+
280
+ const referenceBlockParent = editor.getParentBlock(referenceBlock);
281
+ if (!checkPlacementIsValid(referenceBlockParent)) {
282
+ return getMoveDownPlacement(
283
+ editor,
284
+ placement === "before"
285
+ ? referenceBlock
286
+ : editor.getNextBlock(referenceBlock),
287
+ referenceBlockParent
288
+ );
289
+ }
290
+
291
+ return { referenceBlock, placement };
292
+ }
293
+
294
+ export function moveBlocksUp(editor: BlockNoteEditor<any, any, any>) {
295
+ const selection = editor.getSelection();
296
+ const block = selection?.blocks[0] || editor.getTextCursorPosition().block;
297
+
298
+ const moveUpPlacement = getMoveUpPlacement(
299
+ editor,
300
+ editor.getPrevBlock(block),
301
+ editor.getParentBlock(block)
302
+ );
303
+
304
+ if (!moveUpPlacement) {
305
+ return;
306
+ }
307
+
308
+ moveSelectedBlocksAndSelection(
309
+ editor,
310
+ moveUpPlacement.referenceBlock,
311
+ moveUpPlacement.placement
312
+ );
313
+ }
314
+
315
+ export function moveBlocksDown(editor: BlockNoteEditor<any, any, any>) {
316
+ const selection = editor.getSelection();
317
+ const block =
318
+ selection?.blocks[selection?.blocks.length - 1] ||
319
+ editor.getTextCursorPosition().block;
320
+
321
+ const moveDownPlacement = getMoveDownPlacement(
322
+ editor,
323
+ editor.getNextBlock(block),
324
+ editor.getParentBlock(block)
325
+ );
326
+
327
+ if (!moveDownPlacement) {
328
+ return;
329
+ }
330
+
331
+ moveSelectedBlocksAndSelection(
332
+ editor,
333
+ moveDownPlacement.referenceBlock,
334
+ moveDownPlacement.placement
335
+ );
336
+ }
@@ -28,13 +28,17 @@ function setSelectionWithOffset(
28
28
  offset: number
29
29
  ) {
30
30
  const posInfo = getNodeById(targetBlockId, doc);
31
+ if (!posInfo) {
32
+ throw new Error(`Block with ID ${targetBlockId} not found`);
33
+ }
34
+
31
35
  const info = getBlockInfo(posInfo);
32
36
 
33
37
  if (!info.isBlockContainer) {
34
38
  throw new Error("Target block is not a block container");
35
39
  }
36
40
 
37
- getEditor()._tiptapEditor.view.dispatch(
41
+ getEditor().dispatch(
38
42
  getEditor()._tiptapEditor.state.tr.setSelection(
39
43
  TextSelection.create(doc, info.blockContent.beforePos + offset + 1)
40
44
  )
@@ -263,15 +263,23 @@ export function updateBlock<
263
263
 
264
264
  const id =
265
265
  typeof blockToUpdate === "string" ? blockToUpdate : blockToUpdate.id;
266
- const { posBeforeNode } = getNodeById(id, ttEditor.state.doc);
266
+
267
+ const posInfo = getNodeById(id, ttEditor.state.doc);
268
+ if (!posInfo) {
269
+ throw new Error(`Block with ID ${id} not found`);
270
+ }
267
271
 
268
272
  ttEditor.commands.command(({ state, dispatch }) => {
269
- updateBlockCommand(editor, posBeforeNode, update)({ state, dispatch });
273
+ updateBlockCommand(
274
+ editor,
275
+ posInfo.posBeforeNode,
276
+ update
277
+ )({ state, dispatch });
270
278
  return true;
271
279
  });
272
280
 
273
281
  const blockContainerNode = ttEditor.state.doc
274
- .resolve(posBeforeNode + 1) // TODO: clean?
282
+ .resolve(posInfo.posBeforeNode + 1) // TODO: clean?
275
283
  .node();
276
284
 
277
285
  return nodeToBlock(