@blocknote/core 0.16.0 → 0.17.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 (248) hide show
  1. package/dist/blocknote.js +3287 -2755
  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/webpack-stats.json +1 -1
  6. package/package.json +5 -2
  7. package/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap +3087 -0
  8. package/src/api/blockManipulation/commands/insertBlocks/insertBlocks.test.ts +132 -0
  9. package/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts +71 -0
  10. package/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap +2276 -0
  11. package/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts +131 -0
  12. package/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts +103 -0
  13. package/src/api/blockManipulation/commands/moveBlock/__snapshots__/moveBlock.test.ts.snap +3767 -0
  14. package/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts +192 -0
  15. package/src/api/blockManipulation/commands/moveBlock/moveBlock.ts +178 -0
  16. package/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap +1136 -0
  17. package/src/api/blockManipulation/commands/removeBlocks/removeBlocks.test.ts +34 -0
  18. package/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts +100 -0
  19. package/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap +4931 -0
  20. package/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.test.ts +222 -0
  21. package/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts +70 -0
  22. package/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap +2924 -0
  23. package/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts +136 -0
  24. package/src/api/blockManipulation/commands/splitBlock/splitBlock.ts +48 -0
  25. package/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap +8376 -0
  26. package/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts +300 -0
  27. package/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +199 -0
  28. package/src/api/blockManipulation/insertContentAt.ts +96 -0
  29. package/src/api/blockManipulation/selections/textCursorPosition/__snapshots__/textCursorPosition.test.ts.snap +316 -0
  30. package/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.test.ts +53 -0
  31. package/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts +130 -0
  32. package/src/api/blockManipulation/setupTestEnv.ts +179 -0
  33. package/src/api/clipboard/__snapshots__/tableAllCells.html +1 -1
  34. package/src/api/clipboard/clipboard.test.ts +5 -6
  35. package/src/api/clipboard/fromClipboard/fileDropExtension.ts +8 -4
  36. package/src/api/clipboard/fromClipboard/handleFileInsertion.ts +11 -6
  37. package/src/api/clipboard/fromClipboard/pasteExtension.ts +8 -4
  38. package/src/api/clipboard/toClipboard/copyExtension.ts +113 -61
  39. package/src/api/exporters/html/__snapshots__/complex/misc/external.html +1 -1
  40. package/src/api/exporters/html/__snapshots__/lists/basic/external.html +1 -1
  41. package/src/api/exporters/html/__snapshots__/lists/nested/external.html +1 -1
  42. package/src/api/exporters/html/externalHTMLExporter.ts +42 -94
  43. package/src/api/exporters/html/htmlConversion.test.ts +19 -13
  44. package/src/api/exporters/html/internalHTMLSerializer.ts +21 -72
  45. package/src/api/exporters/html/util/serializeBlocksExternalHTML.ts +263 -0
  46. package/src/api/exporters/html/util/serializeBlocksInternalHTML.ts +158 -0
  47. package/src/api/exporters/markdown/markdownExporter.test.ts +10 -10
  48. package/src/api/exporters/markdown/markdownExporter.ts +11 -7
  49. package/src/api/exporters/markdown/util/addSpacesToCheckboxesRehypePlugin.ts +2 -2
  50. package/src/api/getBlockInfoFromPos.ts +172 -90
  51. package/src/api/nodeConversions/blockToNode.ts +257 -0
  52. package/src/api/nodeConversions/fragmentToBlocks.ts +60 -0
  53. package/src/api/nodeConversions/nodeConversions.test.ts +9 -8
  54. package/src/api/nodeConversions/{nodeConversions.ts → nodeToBlock.ts} +20 -262
  55. package/src/api/parsers/html/parseHTML.test.ts +2 -2
  56. package/src/api/parsers/html/parseHTML.ts +8 -4
  57. package/src/api/parsers/html/util/nestedLists.test.ts +2 -2
  58. package/src/api/parsers/markdown/parseMarkdown.test.ts +2 -2
  59. package/src/api/parsers/markdown/parseMarkdown.ts +8 -4
  60. package/src/api/testUtil/cases/customBlocks.ts +11 -11
  61. package/src/api/testUtil/cases/customInlineContent.ts +6 -6
  62. package/src/api/testUtil/cases/customStyles.ts +6 -6
  63. package/src/api/testUtil/cases/defaultSchema.ts +4 -4
  64. package/src/api/testUtil/index.ts +6 -6
  65. package/src/api/testUtil/partialBlockTestUtil.ts +5 -5
  66. package/src/blocks/AudioBlockContent/AudioBlockContent.ts +5 -5
  67. package/src/blocks/FileBlockContent/FileBlockContent.ts +4 -4
  68. package/src/blocks/FileBlockContent/fileBlockHelpers.ts +2 -2
  69. package/src/blocks/HeadingBlockContent/HeadingBlockContent.ts +61 -39
  70. package/src/blocks/ImageBlockContent/ImageBlockContent.ts +5 -5
  71. package/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +30 -18
  72. package/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts +67 -33
  73. package/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts +23 -19
  74. package/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts +22 -24
  75. package/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +31 -19
  76. package/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts +16 -11
  77. package/src/blocks/TableBlockContent/TableBlockContent.ts +4 -4
  78. package/src/blocks/VideoBlockContent/VideoBlockContent.ts +5 -5
  79. package/src/blocks/defaultBlockHelpers.ts +4 -4
  80. package/src/blocks/defaultBlockTypeGuards.ts +5 -5
  81. package/src/blocks/defaultBlocks.ts +13 -13
  82. package/src/blocks/defaultProps.ts +1 -1
  83. package/src/editor/BlockNoteEditor.test.ts +14 -7
  84. package/src/editor/BlockNoteEditor.ts +82 -149
  85. package/src/editor/BlockNoteExtensions.ts +15 -11
  86. package/src/editor/BlockNoteSchema.ts +7 -7
  87. package/src/editor/BlockNoteTipTapEditor.ts +5 -3
  88. package/src/editor/cursorPositionTypes.ts +7 -2
  89. package/src/editor/selectionTypes.ts +6 -2
  90. package/src/extensions/BackgroundColor/BackgroundColorExtension.ts +1 -1
  91. package/src/extensions/BackgroundColor/BackgroundColorMark.ts +1 -1
  92. package/src/extensions/FilePanel/FilePanelPlugin.ts +4 -4
  93. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +8 -4
  94. package/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +333 -0
  95. package/src/extensions/LinkToolbar/LinkToolbarPlugin.ts +9 -4
  96. package/src/extensions/{NonEditableBlocks/NonEditableBlockPlugin.ts → NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.ts} +2 -2
  97. package/src/extensions/Placeholder/PlaceholderPlugin.ts +1 -1
  98. package/src/extensions/SideMenu/SideMenuPlugin.ts +72 -401
  99. package/src/extensions/SideMenu/dragging.ts +251 -0
  100. package/src/extensions/SuggestionMenu/DefaultSuggestionItem.ts +1 -1
  101. package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +8 -4
  102. package/src/extensions/SuggestionMenu/getDefaultEmojiPickerItems.ts +8 -4
  103. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +19 -15
  104. package/src/extensions/TableHandles/TableHandlesPlugin.ts +11 -7
  105. package/src/extensions/TextColor/TextColorExtension.ts +1 -1
  106. package/src/extensions/TextColor/TextColorMark.ts +1 -1
  107. package/src/i18n/dictionary.ts +1 -1
  108. package/src/i18n/locales/ar.ts +1 -1
  109. package/src/i18n/locales/fr.ts +1 -1
  110. package/src/i18n/locales/hr.ts +308 -0
  111. package/src/i18n/locales/index.ts +15 -14
  112. package/src/i18n/locales/is.ts +1 -1
  113. package/src/i18n/locales/ja.ts +1 -1
  114. package/src/i18n/locales/ko.ts +1 -1
  115. package/src/i18n/locales/nl.ts +1 -1
  116. package/src/i18n/locales/pl.ts +1 -1
  117. package/src/i18n/locales/pt.ts +1 -1
  118. package/src/i18n/locales/ru.ts +1 -1
  119. package/src/i18n/locales/vi.ts +1 -1
  120. package/src/i18n/locales/zh.ts +1 -1
  121. package/src/index.ts +45 -44
  122. package/src/pm-nodes/BlockContainer.ts +3 -647
  123. package/src/pm-nodes/BlockGroup.ts +2 -2
  124. package/src/pm-nodes/index.ts +3 -3
  125. package/src/schema/blocks/createSpec.ts +8 -7
  126. package/src/schema/blocks/internal.ts +9 -9
  127. package/src/schema/blocks/types.ts +4 -4
  128. package/src/schema/index.ts +10 -10
  129. package/src/schema/inlineContent/createSpec.ts +9 -10
  130. package/src/schema/inlineContent/internal.ts +3 -3
  131. package/src/schema/inlineContent/types.ts +2 -2
  132. package/src/schema/styles/createSpec.ts +4 -3
  133. package/src/schema/styles/internal.ts +1 -1
  134. package/types/src/api/blockManipulation/commands/insertBlocks/insertBlocks.d.ts +4 -0
  135. package/types/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.d.ts +7 -0
  136. package/types/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.d.ts +1 -0
  137. package/types/src/api/blockManipulation/commands/moveBlock/moveBlock.d.ts +5 -0
  138. package/types/src/api/blockManipulation/commands/moveBlock/moveBlock.test.d.ts +1 -0
  139. package/types/src/api/blockManipulation/commands/removeBlocks/removeBlocks.d.ts +7 -0
  140. package/types/src/api/blockManipulation/commands/removeBlocks/removeBlocks.test.d.ts +1 -0
  141. package/types/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.d.ts +7 -0
  142. package/types/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.test.d.ts +1 -0
  143. package/types/src/api/blockManipulation/commands/splitBlock/splitBlock.d.ts +5 -0
  144. package/types/src/api/blockManipulation/commands/splitBlock/splitBlock.test.d.ts +1 -0
  145. package/types/src/api/blockManipulation/commands/updateBlock/updateBlock.d.ts +11 -0
  146. package/types/src/api/blockManipulation/commands/updateBlock/updateBlock.test.d.ts +1 -0
  147. package/types/src/api/blockManipulation/insertContentAt.d.ts +6 -0
  148. package/types/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.d.ts +5 -0
  149. package/types/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.test.d.ts +1 -0
  150. package/types/src/api/blockManipulation/setupTestEnv.d.ts +492 -0
  151. package/types/src/api/clipboard/fromClipboard/fileDropExtension.d.ts +3 -3
  152. package/types/src/api/clipboard/fromClipboard/handleFileInsertion.d.ts +1 -1
  153. package/types/src/api/clipboard/fromClipboard/pasteExtension.d.ts +2 -2
  154. package/types/src/api/clipboard/toClipboard/copyExtension.d.ts +5 -5
  155. package/types/src/api/exporters/html/externalHTMLExporter.d.ts +7 -9
  156. package/types/src/api/exporters/html/internalHTMLSerializer.d.ts +6 -10
  157. package/types/src/api/exporters/html/util/serializeBlocksExternalHTML.d.ts +10 -0
  158. package/types/src/api/exporters/html/util/serializeBlocksInternalHTML.d.ts +11 -0
  159. package/types/src/api/exporters/markdown/markdownExporter.d.ts +3 -3
  160. package/types/src/api/getBlockInfoFromPos.d.ts +63 -20
  161. package/types/src/api/nodeConversions/blockToNode.d.ts +15 -0
  162. package/types/src/api/nodeConversions/fragmentToBlocks.d.ts +7 -0
  163. package/types/src/api/nodeConversions/nodeToBlock.d.ts +16 -0
  164. package/types/src/api/parsers/html/parseHTML.d.ts +2 -2
  165. package/types/src/api/parsers/markdown/parseMarkdown.d.ts +2 -2
  166. package/types/src/api/testUtil/cases/customBlocks.d.ts +39 -39
  167. package/types/src/api/testUtil/cases/customInlineContent.d.ts +35 -35
  168. package/types/src/api/testUtil/cases/customStyles.d.ts +35 -35
  169. package/types/src/api/testUtil/cases/defaultSchema.d.ts +2 -2
  170. package/types/src/api/testUtil/index.d.ts +6 -6
  171. package/types/src/api/testUtil/partialBlockTestUtil.d.ts +4 -4
  172. package/types/src/blocks/AudioBlockContent/AudioBlockContent.d.ts +4 -4
  173. package/types/src/blocks/FileBlockContent/FileBlockContent.d.ts +4 -4
  174. package/types/src/blocks/FileBlockContent/fileBlockHelpers.d.ts +2 -2
  175. package/types/src/blocks/HeadingBlockContent/HeadingBlockContent.d.ts +2 -2
  176. package/types/src/blocks/ImageBlockContent/ImageBlockContent.d.ts +4 -4
  177. package/types/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.d.ts +2 -2
  178. package/types/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.d.ts +2 -2
  179. package/types/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.d.ts +2 -2
  180. package/types/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.d.ts +2 -2
  181. package/types/src/blocks/ParagraphBlockContent/ParagraphBlockContent.d.ts +2 -2
  182. package/types/src/blocks/TableBlockContent/TableBlockContent.d.ts +2 -2
  183. package/types/src/blocks/VideoBlockContent/VideoBlockContent.d.ts +4 -4
  184. package/types/src/blocks/defaultBlockHelpers.d.ts +3 -3
  185. package/types/src/blocks/defaultBlockTypeGuards.d.ts +4 -4
  186. package/types/src/blocks/defaultBlocks.d.ts +38 -38
  187. package/types/src/blocks/defaultProps.d.ts +1 -1
  188. package/types/src/editor/BlockNoteEditor.d.ts +28 -16
  189. package/types/src/editor/BlockNoteExtensions.d.ts +3 -3
  190. package/types/src/editor/BlockNoteSchema.d.ts +4 -4
  191. package/types/src/editor/BlockNoteTipTapEditor.d.ts +2 -2
  192. package/types/src/editor/cursorPositionTypes.d.ts +3 -2
  193. package/types/src/editor/selectionTypes.d.ts +2 -2
  194. package/types/src/extensions/BackgroundColor/BackgroundColorMark.d.ts +1 -1
  195. package/types/src/extensions/FilePanel/FilePanelPlugin.d.ts +4 -4
  196. package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +4 -4
  197. package/types/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.d.ts +5 -0
  198. package/types/src/extensions/LinkToolbar/LinkToolbarPlugin.d.ts +4 -4
  199. package/types/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.d.ts +2 -0
  200. package/types/src/extensions/Placeholder/PlaceholderPlugin.d.ts +1 -1
  201. package/types/src/extensions/SideMenu/SideMenuPlugin.d.ts +12 -28
  202. package/types/src/extensions/SideMenu/dragging.d.ts +17 -0
  203. package/types/src/extensions/SuggestionMenu/DefaultSuggestionItem.d.ts +1 -1
  204. package/types/src/extensions/SuggestionMenu/SuggestionPlugin.d.ts +4 -4
  205. package/types/src/extensions/SuggestionMenu/getDefaultEmojiPickerItems.d.ts +3 -3
  206. package/types/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.d.ts +4 -4
  207. package/types/src/extensions/TableHandles/TableHandlesPlugin.d.ts +4 -4
  208. package/types/src/extensions/TextColor/TextColorMark.d.ts +1 -1
  209. package/types/src/i18n/dictionary.d.ts +1 -1
  210. package/types/src/i18n/locales/ar.d.ts +1 -1
  211. package/types/src/i18n/locales/fr.d.ts +1 -1
  212. package/types/src/i18n/locales/hr.d.ts +239 -0
  213. package/types/src/i18n/locales/index.d.ts +15 -14
  214. package/types/src/i18n/locales/is.d.ts +1 -1
  215. package/types/src/i18n/locales/ja.d.ts +1 -1
  216. package/types/src/i18n/locales/ko.d.ts +1 -1
  217. package/types/src/i18n/locales/nl.d.ts +1 -1
  218. package/types/src/i18n/locales/pl.d.ts +1 -1
  219. package/types/src/i18n/locales/pt.d.ts +1 -1
  220. package/types/src/i18n/locales/ru.d.ts +1 -1
  221. package/types/src/i18n/locales/vi.d.ts +1 -1
  222. package/types/src/i18n/locales/zh.d.ts +1 -1
  223. package/types/src/index.d.ts +45 -44
  224. package/types/src/pm-nodes/BlockContainer.d.ts +2 -16
  225. package/types/src/pm-nodes/BlockGroup.d.ts +1 -1
  226. package/types/src/pm-nodes/index.d.ts +3 -3
  227. package/types/src/schema/blocks/createSpec.d.ts +5 -5
  228. package/types/src/schema/blocks/internal.d.ts +5 -5
  229. package/types/src/schema/blocks/types.d.ts +4 -4
  230. package/types/src/schema/index.d.ts +10 -10
  231. package/types/src/schema/inlineContent/createSpec.d.ts +3 -3
  232. package/types/src/schema/inlineContent/internal.d.ts +2 -2
  233. package/types/src/schema/inlineContent/types.d.ts +2 -2
  234. package/types/src/schema/styles/createSpec.d.ts +1 -1
  235. package/types/src/schema/styles/internal.d.ts +1 -1
  236. package/src/api/blockManipulation/__snapshots__/blockManipulation.test.ts.snap +0 -714
  237. package/src/api/blockManipulation/blockManipulation.test.ts +0 -292
  238. package/src/api/blockManipulation/blockManipulation.ts +0 -350
  239. package/src/api/exporters/html/util/sharedHTMLConversion.ts +0 -130
  240. package/src/api/exporters/html/util/simplifyBlocksRehypePlugin.ts +0 -218
  241. package/src/api/getCurrentBlockContentType.ts +0 -14
  242. package/types/src/api/blockManipulation/blockManipulation.d.ts +0 -14
  243. package/types/src/api/exporters/html/util/sharedHTMLConversion.d.ts +0 -9
  244. package/types/src/api/exporters/html/util/simplifyBlocksRehypePlugin.d.ts +0 -16
  245. package/types/src/api/getCurrentBlockContentType.d.ts +0 -2
  246. package/types/src/api/nodeConversions/nodeConversions.d.ts +0 -24
  247. package/types/src/extensions/NonEditableBlocks/NonEditableBlockPlugin.d.ts +0 -2
  248. /package/types/src/api/blockManipulation/{blockManipulation.test.d.ts → commands/insertBlocks/insertBlocks.test.d.ts} +0 -0
@@ -1,114 +1,196 @@
1
- import { Node, NodeType } from "prosemirror-model";
1
+ import { Node, ResolvedPos } from "prosemirror-model";
2
+ import { EditorState } from "prosemirror-state";
2
3
 
3
- export type BlockInfoWithoutPositions = {
4
- id: string;
4
+ type SingleBlockInfo = {
5
5
  node: Node;
6
- contentNode: Node;
7
- contentType: NodeType;
8
- numChildBlocks: number;
6
+ beforePos: number;
7
+ afterPos: number;
9
8
  };
10
9
 
11
- export type BlockInfo = BlockInfoWithoutPositions & {
12
- startPos: number;
13
- endPos: number;
14
- depth: number;
10
+ export type BlockInfo = {
11
+ blockContainer: SingleBlockInfo;
12
+ blockContent: SingleBlockInfo;
13
+ blockGroup?: SingleBlockInfo;
15
14
  };
16
15
 
17
16
  /**
18
- * Helper function for `getBlockInfoFromPos`, returns information regarding
19
- * provided blockContainer node.
20
- * @param blockContainer The blockContainer node to retrieve info for.
17
+ * Retrieves the position just before the nearest blockContainer node in a
18
+ * ProseMirror doc, relative to a position. If the position is within a
19
+ * blockContainer node or its descendants, the position just before it is
20
+ * returned. If the position is not within a blockContainer node or its
21
+ * descendants, the position just before the next closest blockContainer node
22
+ * is returned. If the position is beyond the last blockContainer, the position
23
+ * just before the last blockContainer is returned.
24
+ * @param doc The ProseMirror doc.
25
+ * @param pos An integer position in the document.
26
+ * @returns The position just before the nearest blockContainer node.
21
27
  */
22
- export function getBlockInfo(blockContainer: Node): BlockInfoWithoutPositions {
23
- const id = blockContainer.attrs["id"];
24
- const contentNode = blockContainer.firstChild!;
25
- const contentType = contentNode.type;
26
- const numChildBlocks =
27
- blockContainer.childCount === 2 ? blockContainer.lastChild!.childCount : 0;
28
+ export function getNearestBlockContainerPos(doc: Node, pos: number) {
29
+ const $pos = doc.resolve(pos);
28
30
 
29
- return {
30
- id,
31
- node: blockContainer,
32
- contentNode,
33
- contentType,
34
- numChildBlocks,
35
- };
36
- }
31
+ // Checks if the position provided is already just before a blockContainer
32
+ // node, in which case we return the position.
33
+ if ($pos.nodeAfter && $pos.nodeAfter.type.name === "blockContainer") {
34
+ return {
35
+ posBeforeNode: $pos.pos,
36
+ node: $pos.nodeAfter,
37
+ };
38
+ }
37
39
 
38
- /**
39
- * Retrieves information regarding the nearest blockContainer node in a
40
- * ProseMirror doc, relative to a position.
41
- * @param doc The ProseMirror doc.
42
- * @param pos An integer position.
43
- * @returns A BlockInfo object for the nearest blockContainer node.
44
- */
45
- export function getBlockInfoFromPos(doc: Node, pos: number): BlockInfo {
46
- // If the position is outside the outer block group, we need to move it to the
47
- // nearest block. This happens when the collaboration plugin is active, where
48
- // the selection is placed at the very end of the doc.
49
- const outerBlockGroupStartPos = 1;
50
- const outerBlockGroupEndPos = doc.nodeSize - 2;
51
- if (pos <= outerBlockGroupStartPos) {
52
- pos = outerBlockGroupStartPos + 1;
53
-
54
- while (
55
- doc.resolve(pos).parent.type.name !== "blockContainer" &&
56
- pos < outerBlockGroupEndPos
57
- ) {
58
- pos++;
59
- }
60
- } else if (pos >= outerBlockGroupEndPos) {
61
- pos = outerBlockGroupEndPos - 1;
62
-
63
- while (
64
- doc.resolve(pos).parent.type.name !== "blockContainer" &&
65
- pos > outerBlockGroupStartPos
66
- ) {
67
- pos--;
40
+ // Checks the node containing the position and its ancestors until a
41
+ // blockContainer node is found and returned.
42
+ let depth = $pos.depth;
43
+ let node = $pos.node(depth);
44
+ while (depth > 0) {
45
+ if (node.type.name === "blockContainer") {
46
+ return {
47
+ posBeforeNode: $pos.before(depth),
48
+ node: node,
49
+ };
68
50
  }
69
- }
70
51
 
71
- // This gets triggered when a node selection on a block is active, i.e. when
72
- // you drag and drop a block.
73
- if (doc.resolve(pos).parent.type.name === "blockGroup") {
74
- pos++;
52
+ depth--;
53
+ node = $pos.node(depth);
75
54
  }
76
55
 
77
- const $pos = doc.resolve(pos);
56
+ // If the position doesn't lie within a blockContainer node, we instead find
57
+ // the position of the next closest one. If the position is beyond the last
58
+ // blockContainer, we return the position of the last blockContainer. While
59
+ // running `doc.descendants` is expensive, this case should be very rarely
60
+ // triggered. However, it's possible for the position to sometimes be beyond
61
+ // the last blockContainer node. This is a problem specifically when using the
62
+ // collaboration plugin.
63
+ const allBlockContainerPositions: number[] = [];
64
+ doc.descendants((node, pos) => {
65
+ if (node.type.name === "blockContainer") {
66
+ allBlockContainerPositions.push(pos);
67
+ }
68
+ });
78
69
 
79
- const maxDepth = $pos.depth;
80
- let node = $pos.node(maxDepth);
81
- let depth = maxDepth;
70
+ // eslint-disable-next-line no-console
71
+ console.warn(`Position ${pos} is not within a blockContainer node.`);
82
72
 
83
- // eslint-disable-next-line no-constant-condition
84
- while (true) {
85
- if (depth < 0) {
86
- throw new Error(
87
- "Could not find blockContainer node. This can only happen if the underlying BlockNote schema has been edited."
88
- );
89
- }
73
+ const resolvedPos = doc.resolve(
74
+ allBlockContainerPositions.find((position) => position >= pos) ||
75
+ allBlockContainerPositions[allBlockContainerPositions.length - 1]
76
+ );
77
+ return {
78
+ posBeforeNode: resolvedPos.pos,
79
+ node: resolvedPos.nodeAfter!,
80
+ };
81
+ }
90
82
 
91
- if (node.type.name === "blockContainer") {
92
- break;
83
+ /**
84
+ * Gets information regarding the ProseMirror nodes that make up a block in a
85
+ * BlockNote document. This includes the main `blockContainer` node, the
86
+ * `blockContent` node with the block's main body, and the optional `blockGroup`
87
+ * node which contains the block's children. As well as the nodes, also returns
88
+ * the ProseMirror positions just before & after each node.
89
+ * @param node The main `blockContainer` node that the block information should
90
+ * be retrieved from,
91
+ * @param blockContainerBeforePosOffset the position just before the
92
+ * `blockContainer` node in the document.
93
+ */
94
+ export function getBlockInfoWithManualOffset(
95
+ node: Node,
96
+ blockContainerBeforePosOffset: number
97
+ ): BlockInfo {
98
+ const blockContainerNode = node;
99
+ const blockContainerBeforePos = blockContainerBeforePosOffset;
100
+ const blockContainerAfterPos =
101
+ blockContainerBeforePos + blockContainerNode.nodeSize;
102
+
103
+ const blockContainer: SingleBlockInfo = {
104
+ node: blockContainerNode,
105
+ beforePos: blockContainerBeforePos,
106
+ afterPos: blockContainerAfterPos,
107
+ };
108
+ let blockContent: SingleBlockInfo | undefined = undefined;
109
+ let blockGroup: SingleBlockInfo | undefined = undefined;
110
+
111
+ blockContainerNode.forEach((node, offset) => {
112
+ if (node.type.spec.group === "blockContent") {
113
+ // console.log(beforePos, offset);
114
+ const blockContentNode = node;
115
+ const blockContentBeforePos = blockContainerBeforePos + offset + 1;
116
+ const blockContentAfterPos = blockContentBeforePos + node.nodeSize;
117
+
118
+ blockContent = {
119
+ node: blockContentNode,
120
+ beforePos: blockContentBeforePos,
121
+ afterPos: blockContentAfterPos,
122
+ };
123
+ } else if (node.type.name === "blockGroup") {
124
+ const blockGroupNode = node;
125
+ const blockGroupBeforePos = blockContainerBeforePos + offset + 1;
126
+ const blockGroupAfterPos = blockGroupBeforePos + node.nodeSize;
127
+
128
+ blockGroup = {
129
+ node: blockGroupNode,
130
+ beforePos: blockGroupBeforePos,
131
+ afterPos: blockGroupAfterPos,
132
+ };
93
133
  }
134
+ });
94
135
 
95
- depth -= 1;
96
- node = $pos.node(depth);
136
+ if (!blockContent) {
137
+ throw new Error(
138
+ `blockContainer node does not contain a blockContent node in its children: ${blockContainerNode}`
139
+ );
97
140
  }
98
141
 
99
- const { id, contentNode, contentType, numChildBlocks } = getBlockInfo(node);
100
-
101
- const startPos = $pos.start(depth);
102
- const endPos = $pos.end(depth);
103
-
104
142
  return {
105
- id,
106
- node,
107
- contentNode,
108
- contentType,
109
- numChildBlocks,
110
- startPos,
111
- endPos,
112
- depth,
143
+ blockContainer,
144
+ blockContent,
145
+ blockGroup,
113
146
  };
114
147
  }
148
+
149
+ /**
150
+ * Gets information regarding the ProseMirror nodes that make up a block in a
151
+ * BlockNote document. This includes the main `blockContainer` node, the
152
+ * `blockContent` node with the block's main body, and the optional `blockGroup`
153
+ * node which contains the block's children. As well as the nodes, also returns
154
+ * the ProseMirror positions just before & after each node.
155
+ * @param posInfo An object with the main `blockContainer` node that the block
156
+ * information should be retrieved from, and the position just before it in the
157
+ * document.
158
+ */
159
+ export function getBlockInfo(posInfo: { posBeforeNode: number; node: Node }) {
160
+ return getBlockInfoWithManualOffset(posInfo.node, posInfo.posBeforeNode);
161
+ }
162
+
163
+ /**
164
+ * Gets information regarding the ProseMirror nodes that make up a block from a
165
+ * resolved position just before the `blockContainer` node in the document that
166
+ * corresponds to it.
167
+ * @param resolvedPos The resolved position just before the `blockContainer`
168
+ * node.
169
+ */
170
+ export function getBlockInfoFromResolvedPos(resolvedPos: ResolvedPos) {
171
+ if (!resolvedPos.nodeAfter) {
172
+ throw new Error(
173
+ `Attempted to get blockContainer node at position ${resolvedPos.pos} but a node at this position does not exist`
174
+ );
175
+ }
176
+ if (resolvedPos.nodeAfter.type.name !== "blockContainer") {
177
+ throw new Error(
178
+ `Attempted to get blockContainer node at position ${resolvedPos.pos} but found node of different type ${resolvedPos.nodeAfter}`
179
+ );
180
+ }
181
+ return getBlockInfoWithManualOffset(resolvedPos.nodeAfter, resolvedPos.pos);
182
+ }
183
+
184
+ /**
185
+ * Gets information regarding the ProseMirror nodes that make up a block. The
186
+ * block chosen is the one currently containing the current ProseMirror
187
+ * selection.
188
+ * @param state The ProseMirror editor state.
189
+ */
190
+ export function getBlockInfoFromSelection(state: EditorState) {
191
+ const posInfo = getNearestBlockContainerPos(
192
+ state.doc,
193
+ state.selection.anchor
194
+ );
195
+ return getBlockInfo(posInfo);
196
+ }
@@ -0,0 +1,257 @@
1
+ import { Mark, Node, Schema } from "@tiptap/pm/model";
2
+
3
+ import UniqueID from "../../extensions/UniqueID/UniqueID.js";
4
+ import type {
5
+ InlineContentSchema,
6
+ PartialCustomInlineContentFromConfig,
7
+ PartialInlineContent,
8
+ PartialLink,
9
+ PartialTableContent,
10
+ StyleSchema,
11
+ StyledText,
12
+ } from "../../schema";
13
+
14
+ import type { PartialBlock } from "../../blocks/defaultBlocks";
15
+ import {
16
+ isPartialLinkInlineContent,
17
+ isStyledTextInlineContent,
18
+ } from "../../schema/inlineContent/types.js";
19
+ import { UnreachableCaseError } from "../../util/typescript.js";
20
+
21
+ /**
22
+ * Convert a StyledText inline element to a
23
+ * prosemirror text node with the appropriate marks
24
+ */
25
+ function styledTextToNodes<T extends StyleSchema>(
26
+ styledText: StyledText<T>,
27
+ schema: Schema,
28
+ styleSchema: T
29
+ ): Node[] {
30
+ const marks: Mark[] = [];
31
+
32
+ for (const [style, value] of Object.entries(styledText.styles)) {
33
+ const config = styleSchema[style];
34
+ if (!config) {
35
+ throw new Error(`style ${style} not found in styleSchema`);
36
+ }
37
+
38
+ if (config.propSchema === "boolean") {
39
+ marks.push(schema.mark(style));
40
+ } else if (config.propSchema === "string") {
41
+ marks.push(schema.mark(style, { stringValue: value }));
42
+ } else {
43
+ throw new UnreachableCaseError(config.propSchema);
44
+ }
45
+ }
46
+
47
+ return (
48
+ styledText.text
49
+ // Splits text & line breaks.
50
+ .split(/(\n)/g)
51
+ // If the content ends with a line break, an empty string is added to the
52
+ // end, which this removes.
53
+ .filter((text) => text.length > 0)
54
+ // Converts text & line breaks to nodes.
55
+ .map((text) => {
56
+ if (text === "\n") {
57
+ return schema.nodes["hardBreak"].create();
58
+ } else {
59
+ return schema.text(text, marks);
60
+ }
61
+ })
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Converts a Link inline content element to
67
+ * prosemirror text nodes with the appropriate marks
68
+ */
69
+ function linkToNodes(
70
+ link: PartialLink<StyleSchema>,
71
+ schema: Schema,
72
+ styleSchema: StyleSchema
73
+ ): Node[] {
74
+ const linkMark = schema.marks.link.create({
75
+ href: link.href,
76
+ });
77
+
78
+ return styledTextArrayToNodes(link.content, schema, styleSchema).map(
79
+ (node) => {
80
+ if (node.type.name === "text") {
81
+ return node.mark([...node.marks, linkMark]);
82
+ }
83
+
84
+ if (node.type.name === "hardBreak") {
85
+ return node;
86
+ }
87
+ throw new Error("unexpected node type");
88
+ }
89
+ );
90
+ }
91
+
92
+ /**
93
+ * Converts an array of StyledText inline content elements to
94
+ * prosemirror text nodes with the appropriate marks
95
+ */
96
+ function styledTextArrayToNodes<S extends StyleSchema>(
97
+ content: string | StyledText<S>[],
98
+ schema: Schema,
99
+ styleSchema: S
100
+ ): Node[] {
101
+ const nodes: Node[] = [];
102
+
103
+ if (typeof content === "string") {
104
+ nodes.push(
105
+ ...styledTextToNodes(
106
+ { type: "text", text: content, styles: {} },
107
+ schema,
108
+ styleSchema
109
+ )
110
+ );
111
+ return nodes;
112
+ }
113
+
114
+ for (const styledText of content) {
115
+ nodes.push(...styledTextToNodes(styledText, schema, styleSchema));
116
+ }
117
+ return nodes;
118
+ }
119
+
120
+ /**
121
+ * converts an array of inline content elements to prosemirror nodes
122
+ */
123
+ export function inlineContentToNodes<
124
+ I extends InlineContentSchema,
125
+ S extends StyleSchema
126
+ >(
127
+ blockContent: PartialInlineContent<I, S>,
128
+ schema: Schema,
129
+ styleSchema: S
130
+ ): Node[] {
131
+ const nodes: Node[] = [];
132
+
133
+ for (const content of blockContent) {
134
+ if (typeof content === "string") {
135
+ nodes.push(...styledTextArrayToNodes(content, schema, styleSchema));
136
+ } else if (isPartialLinkInlineContent(content)) {
137
+ nodes.push(...linkToNodes(content, schema, styleSchema));
138
+ } else if (isStyledTextInlineContent(content)) {
139
+ nodes.push(...styledTextArrayToNodes([content], schema, styleSchema));
140
+ } else {
141
+ nodes.push(
142
+ blockOrInlineContentToContentNode(content, schema, styleSchema)
143
+ );
144
+ }
145
+ }
146
+ return nodes;
147
+ }
148
+
149
+ /**
150
+ * converts an array of inline content elements to prosemirror nodes
151
+ */
152
+ export function tableContentToNodes<
153
+ I extends InlineContentSchema,
154
+ S extends StyleSchema
155
+ >(
156
+ tableContent: PartialTableContent<I, S>,
157
+ schema: Schema,
158
+ styleSchema: StyleSchema
159
+ ): Node[] {
160
+ const rowNodes: Node[] = [];
161
+
162
+ for (const row of tableContent.rows) {
163
+ const columnNodes: Node[] = [];
164
+ for (const cell of row.cells) {
165
+ let pNode: Node;
166
+ if (!cell) {
167
+ pNode = schema.nodes["tableParagraph"].create({});
168
+ } else if (typeof cell === "string") {
169
+ pNode = schema.nodes["tableParagraph"].create({}, schema.text(cell));
170
+ } else {
171
+ const textNodes = inlineContentToNodes(cell, schema, styleSchema);
172
+ pNode = schema.nodes["tableParagraph"].create({}, textNodes);
173
+ }
174
+
175
+ const cellNode = schema.nodes["tableCell"].create({}, pNode);
176
+ columnNodes.push(cellNode);
177
+ }
178
+ const rowNode = schema.nodes["tableRow"].create({}, columnNodes);
179
+ rowNodes.push(rowNode);
180
+ }
181
+ return rowNodes;
182
+ }
183
+
184
+ function blockOrInlineContentToContentNode(
185
+ block:
186
+ | PartialBlock<any, any, any>
187
+ | PartialCustomInlineContentFromConfig<any, any>,
188
+ schema: Schema,
189
+ styleSchema: StyleSchema
190
+ ) {
191
+ let contentNode: Node;
192
+ let type = block.type;
193
+
194
+ // TODO: needed? came from previous code
195
+ if (type === undefined) {
196
+ type = "paragraph";
197
+ }
198
+
199
+ if (!schema.nodes[type]) {
200
+ throw new Error(`node type ${type} not found in schema`);
201
+ }
202
+
203
+ if (!block.content) {
204
+ contentNode = schema.nodes[type].create(block.props);
205
+ } else if (typeof block.content === "string") {
206
+ const nodes = inlineContentToNodes([block.content], schema, styleSchema);
207
+ contentNode = schema.nodes[type].create(block.props, nodes);
208
+ } else if (Array.isArray(block.content)) {
209
+ const nodes = inlineContentToNodes(block.content, schema, styleSchema);
210
+ contentNode = schema.nodes[type].create(block.props, nodes);
211
+ } else if (block.content.type === "tableContent") {
212
+ const nodes = tableContentToNodes(block.content, schema, styleSchema);
213
+ contentNode = schema.nodes[type].create(block.props, nodes);
214
+ } else {
215
+ throw new UnreachableCaseError(block.content.type);
216
+ }
217
+ return contentNode;
218
+ }
219
+
220
+ /**
221
+ * Converts a BlockNote block to a TipTap node.
222
+ */
223
+ export function blockToNode(
224
+ block: PartialBlock<any, any, any>,
225
+ schema: Schema,
226
+ styleSchema: StyleSchema
227
+ ) {
228
+ let id = block.id;
229
+
230
+ if (id === undefined) {
231
+ id = UniqueID.options.generateID();
232
+ }
233
+
234
+ const contentNode = blockOrInlineContentToContentNode(
235
+ block,
236
+ schema,
237
+ styleSchema
238
+ );
239
+
240
+ const children: Node[] = [];
241
+
242
+ if (block.children) {
243
+ for (const child of block.children) {
244
+ children.push(blockToNode(child, schema, styleSchema));
245
+ }
246
+ }
247
+
248
+ const groupNode = schema.nodes["blockGroup"].create({}, children);
249
+
250
+ return schema.nodes["blockContainer"].create(
251
+ {
252
+ id: id,
253
+ ...block.props,
254
+ },
255
+ children.length > 0 ? [contentNode, groupNode] : contentNode
256
+ );
257
+ }
@@ -0,0 +1,60 @@
1
+ import { Fragment } from "@tiptap/pm/model";
2
+ import { BlockNoteSchema } from "../../editor/BlockNoteSchema.js";
3
+ import {
4
+ BlockNoDefaults,
5
+ BlockSchema,
6
+ InlineContentSchema,
7
+ StyleSchema,
8
+ } from "../../schema/index.js";
9
+ import { nodeToBlock } from "./nodeToBlock.js";
10
+
11
+ /**
12
+ * Converts all Blocks within a fragment to BlockNote blocks.
13
+ */
14
+ export function fragmentToBlocks<
15
+ B extends BlockSchema,
16
+ I extends InlineContentSchema,
17
+ S extends StyleSchema
18
+ >(fragment: Fragment, schema: BlockNoteSchema<B, I, S>) {
19
+ // first convert selection to blocknote-style blocks, and then
20
+ // pass these to the exporter
21
+ const blocks: BlockNoDefaults<B, I, S>[] = [];
22
+ fragment.descendants((node) => {
23
+ if (node.type.name === "blockContainer") {
24
+ if (node.firstChild?.type.name === "blockGroup") {
25
+ // selection started within a block group
26
+ // in this case the fragment starts with:
27
+ // <blockContainer>
28
+ // <blockGroup>
29
+ // <blockContainer ... />
30
+ // <blockContainer ... />
31
+ // </blockGroup>
32
+ // </blockContainer>
33
+ //
34
+ // instead of:
35
+ // <blockContainer>
36
+ // <blockContent ... />
37
+ // <blockGroup>
38
+ // <blockContainer ... />
39
+ // <blockContainer ... />
40
+ // </blockGroup>
41
+ // </blockContainer>
42
+ //
43
+ // so we don't need to serialize this block, just descend into the children of the blockGroup
44
+ return true;
45
+ }
46
+ blocks.push(
47
+ nodeToBlock(
48
+ node,
49
+ schema.blockSchema,
50
+ schema.inlineContentSchema,
51
+ schema.styleSchema
52
+ )
53
+ );
54
+ // don't descend into children, as they're already included in the block returned by nodeToBlock
55
+ return false;
56
+ }
57
+ return true;
58
+ });
59
+ return blocks;
60
+ }
@@ -1,17 +1,18 @@
1
1
  import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
2
 
3
- import { BlockNoteEditor } from "../../editor/BlockNoteEditor";
3
+ import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
4
4
 
5
- import { PartialBlock } from "../../blocks/defaultBlocks";
6
- import { customBlocksTestCases } from "../testUtil/cases/customBlocks";
7
- import { customInlineContentTestCases } from "../testUtil/cases/customInlineContent";
8
- import { customStylesTestCases } from "../testUtil/cases/customStyles";
9
- import { defaultSchemaTestCases } from "../testUtil/cases/defaultSchema";
5
+ import { PartialBlock } from "../../blocks/defaultBlocks.js";
6
+ import { customBlocksTestCases } from "../testUtil/cases/customBlocks.js";
7
+ import { customInlineContentTestCases } from "../testUtil/cases/customInlineContent.js";
8
+ import { customStylesTestCases } from "../testUtil/cases/customStyles.js";
9
+ import { defaultSchemaTestCases } from "../testUtil/cases/defaultSchema.js";
10
10
  import {
11
11
  addIdsToBlock,
12
12
  partialBlockToBlockForTesting,
13
- } from "../testUtil/partialBlockTestUtil";
14
- import { blockToNode, nodeToBlock } from "./nodeConversions";
13
+ } from "../testUtil/partialBlockTestUtil.js";
14
+ import { blockToNode } from "./blockToNode.js";
15
+ import { nodeToBlock } from "./nodeToBlock.js";
15
16
 
16
17
  function validateConversion(
17
18
  block: PartialBlock<any, any, any>,