@blocknote/core 0.30.1 → 0.31.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 (139) hide show
  1. package/dist/blocknote.cjs +9 -9
  2. package/dist/blocknote.cjs.map +1 -1
  3. package/dist/blocknote.js +2754 -2230
  4. package/dist/blocknote.js.map +1 -1
  5. package/dist/{en-D4taoCs4.cjs → en-BXVKCwYt.cjs} +2 -2
  6. package/dist/en-BXVKCwYt.cjs.map +1 -0
  7. package/dist/{en-B7ycW7c8.js → en-qGo6sk9V.js} +2 -3
  8. package/dist/en-qGo6sk9V.js.map +1 -0
  9. package/dist/locales.cjs +1 -1
  10. package/dist/locales.cjs.map +1 -1
  11. package/dist/locales.js +20 -39
  12. package/dist/locales.js.map +1 -1
  13. package/dist/style.css +1 -1
  14. package/dist/webpack-stats.json +1 -1
  15. package/package.json +4 -5
  16. package/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts +2 -3
  17. package/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts +1 -1
  18. package/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap +2816 -0
  19. package/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts +158 -0
  20. package/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +87 -17
  21. package/src/api/blockManipulation/selections/selection.ts +48 -1
  22. package/src/api/blockManipulation/selections/{textCursorPosition/textCursorPosition.ts → textCursorPosition.ts} +7 -7
  23. package/src/api/getBlockInfoFromPos.ts +1 -1
  24. package/src/api/nodeConversions/blockToNode.ts +5 -2
  25. package/src/api/nodeConversions/nodeToBlock.ts +203 -8
  26. package/src/api/pmUtil.ts +3 -3
  27. package/src/blocks/CodeBlockContent/CodeBlockContent.ts +6 -6
  28. package/src/blocks/FileBlockContent/helpers/render/createAddFileButton.ts +1 -1
  29. package/src/blocks/TableBlockContent/TableBlockContent.ts +32 -2
  30. package/src/editor/Block.css +27 -1
  31. package/src/editor/BlockNoteEditor.test.ts +7 -0
  32. package/src/editor/BlockNoteEditor.ts +88 -37
  33. package/src/editor/BlockNoteExtension.ts +26 -0
  34. package/src/editor/BlockNoteExtensions.ts +28 -12
  35. package/src/editor/BlockNoteTipTapEditor.ts +23 -2
  36. package/src/extensions/Collaboration/CursorPlugin.ts +13 -7
  37. package/src/extensions/Collaboration/ForkYDocPlugin.test.ts +166 -0
  38. package/src/extensions/Collaboration/ForkYDocPlugin.ts +174 -0
  39. package/src/extensions/Collaboration/SyncPlugin.ts +7 -4
  40. package/src/extensions/Collaboration/UndoPlugin.ts +7 -4
  41. package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor-forked.json +30 -0
  42. package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor.json +30 -0
  43. package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-forked.html +1 -0
  44. package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap.html +1 -0
  45. package/src/extensions/Comments/CommentsPlugin.ts +75 -70
  46. package/src/extensions/FilePanel/FilePanelPlugin.ts +50 -49
  47. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +56 -26
  48. package/src/extensions/LinkToolbar/LinkToolbarPlugin.ts +22 -21
  49. package/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.ts +45 -42
  50. package/src/extensions/Placeholder/PlaceholderPlugin.ts +111 -108
  51. package/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.ts +179 -170
  52. package/src/extensions/ShowSelection/ShowSelectionPlugin.ts +22 -19
  53. package/src/extensions/SideMenu/SideMenuPlugin.ts +19 -18
  54. package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +168 -168
  55. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +4 -4
  56. package/src/extensions/Suggestions/SuggestionMarks.ts +175 -0
  57. package/src/extensions/TableHandles/TableHandlesPlugin.ts +153 -150
  58. package/src/i18n/locales/ar.ts +0 -1
  59. package/src/i18n/locales/de.ts +0 -1
  60. package/src/i18n/locales/en.ts +0 -1
  61. package/src/i18n/locales/es.ts +0 -1
  62. package/src/i18n/locales/fr.ts +0 -1
  63. package/src/i18n/locales/hr.ts +0 -1
  64. package/src/i18n/locales/is.ts +0 -1
  65. package/src/i18n/locales/it.ts +0 -1
  66. package/src/i18n/locales/ja.ts +0 -1
  67. package/src/i18n/locales/ko.ts +0 -1
  68. package/src/i18n/locales/nl.ts +0 -1
  69. package/src/i18n/locales/no.ts +0 -1
  70. package/src/i18n/locales/pl.ts +0 -1
  71. package/src/i18n/locales/pt.ts +0 -1
  72. package/src/i18n/locales/ru.ts +0 -1
  73. package/src/i18n/locales/sk.ts +0 -1
  74. package/src/i18n/locales/uk.ts +0 -1
  75. package/src/i18n/locales/vi.ts +0 -1
  76. package/src/i18n/locales/zh-tw.ts +0 -1
  77. package/src/i18n/locales/zh.ts +0 -1
  78. package/src/index.ts +18 -8
  79. package/src/pm-nodes/BlockContainer.ts +1 -1
  80. package/src/pm-nodes/BlockGroup.ts +1 -1
  81. package/src/pm-nodes/Doc.ts +1 -0
  82. package/types/src/api/blockManipulation/commands/insertBlocks/insertBlocks.d.ts +1 -1
  83. package/types/src/api/blockManipulation/commands/removeBlocks/removeBlocks.d.ts +4 -0
  84. package/types/src/api/blockManipulation/commands/removeBlocks/removeBlocks.test.d.ts +1 -0
  85. package/types/src/api/blockManipulation/commands/updateBlock/updateBlock.d.ts +3 -1
  86. package/types/src/api/blockManipulation/selections/selection.d.ts +10 -0
  87. package/types/src/api/blockManipulation/selections/textCursorPosition.d.ts +5 -0
  88. package/types/src/api/blockManipulation/transactions.test.d.ts +0 -0
  89. package/types/src/api/clipboard/clipboardExternal.test.d.ts +1 -0
  90. package/types/src/api/clipboard/clipboardInternal.test.d.ts +1 -0
  91. package/types/src/api/clipboard/testUtil.d.ts +541 -0
  92. package/types/src/api/exporters/html/htmlConversion.test.d.ts +1 -0
  93. package/types/src/api/exporters/markdown/markdownExporter.test.d.ts +1 -0
  94. package/types/src/api/nodeConversions/nodeConversions.test.d.ts +1 -0
  95. package/types/src/api/nodeConversions/nodeToBlock.d.ts +39 -2
  96. package/types/src/api/parsers/html/parseHTML.test.d.ts +1 -0
  97. package/types/src/api/parsers/markdown/parseMarkdown.test.d.ts +1 -0
  98. package/types/src/api/pmUtil.d.ts +3 -3
  99. package/types/src/api/testUtil/cases/customBlocks.d.ts +670 -0
  100. package/types/src/api/testUtil/cases/customInlineContent.d.ts +558 -0
  101. package/types/src/api/testUtil/cases/customStyles.d.ts +552 -0
  102. package/types/src/api/testUtil/cases/defaultSchema.d.ts +4 -0
  103. package/types/src/api/testUtil/index.d.ts +14 -0
  104. package/types/src/api/testUtil/partialBlockTestUtil.d.ts +9 -0
  105. package/types/src/api/testUtil/paste.d.ts +2 -0
  106. package/types/src/blocks/CodeBlockContent/defaultSupportedLanguages.d.ts +6 -0
  107. package/types/src/blocks/TableBlockContent/TableBlockContent.d.ts +9 -1
  108. package/types/src/editor/BlockNoteEditor.d.ts +55 -9
  109. package/types/src/editor/BlockNoteExtension.d.ts +9 -0
  110. package/types/src/editor/BlockNoteExtensions.d.ts +2 -2
  111. package/types/src/editor/BlockNoteTipTapEditor.d.ts +2 -2
  112. package/types/src/extensions/Collaboration/CursorPlugin.d.ts +3 -3
  113. package/types/src/extensions/Collaboration/ForkYDocPlugin.d.ts +41 -0
  114. package/types/src/extensions/Collaboration/ForkYDocPlugin.test.d.ts +1 -0
  115. package/types/src/extensions/Collaboration/SyncPlugin.d.ts +3 -3
  116. package/types/src/extensions/Collaboration/UndoPlugin.d.ts +3 -3
  117. package/types/src/extensions/Collaboration/createCollaborationExtensions.d.ts +17 -0
  118. package/types/src/extensions/Comments/CommentsPlugin.d.ts +2 -4
  119. package/types/src/extensions/FilePanel/FilePanelPlugin.d.ts +3 -4
  120. package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +5 -5
  121. package/types/src/extensions/LinkToolbar/LinkToolbarPlugin.d.ts +3 -4
  122. package/types/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.d.ts +2 -3
  123. package/types/src/extensions/Placeholder/PlaceholderPlugin.d.ts +2 -3
  124. package/types/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.d.ts +2 -3
  125. package/types/src/extensions/ShowSelection/ShowSelectionPlugin.d.ts +2 -3
  126. package/types/src/extensions/SideMenu/SideMenuPlugin.d.ts +3 -4
  127. package/types/src/extensions/SuggestionMenu/SuggestionPlugin.d.ts +2 -4
  128. package/types/src/extensions/Suggestions/SuggestionMarks.d.ts +4 -0
  129. package/types/src/extensions/TableHandles/TableHandlesPlugin.d.ts +5 -6
  130. package/types/src/i18n/locales/en.d.ts +0 -1
  131. package/types/src/i18n/locales/sk.d.ts +0 -1
  132. package/types/src/index.d.ts +15 -8
  133. package/dist/en-B7ycW7c8.js.map +0 -1
  134. package/dist/en-D4taoCs4.cjs.map +0 -1
  135. package/dist/tsconfig.tsbuildinfo +0 -1
  136. package/src/api/blockManipulation/selections/__snapshots__/selection.test.ts.snap +0 -844
  137. package/src/api/blockManipulation/selections/selection.test.ts +0 -72
  138. package/src/api/blockManipulation/selections/textCursorPosition/__snapshots__/textCursorPosition.test.ts.snap +0 -316
  139. package/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.test.ts +0 -74
@@ -1,5 +1,5 @@
1
- import { Mark, Node, Schema } from "@tiptap/pm/model";
2
-
1
+ import { Mark, Node, Schema, Slice } from "@tiptap/pm/model";
2
+ import type { Block } from "../../blocks/defaultBlocks.js";
3
3
  import UniqueID from "../../extensions/UniqueID/UniqueID.js";
4
4
  import type {
5
5
  BlockSchema,
@@ -13,17 +13,18 @@ import type {
13
13
  TableCell,
14
14
  TableContent,
15
15
  } from "../../schema/index.js";
16
- import { getBlockInfoWithManualOffset } from "../getBlockInfoFromPos.js";
17
-
18
- import type { Block } from "../../blocks/defaultBlocks.js";
19
16
  import {
20
17
  isLinkInlineContent,
21
18
  isStyledTextInlineContent,
22
19
  } from "../../schema/inlineContent/types.js";
23
20
  import { UnreachableCaseError } from "../../util/typescript.js";
24
- import { getBlockCache, getStyleSchema } from "../pmUtil.js";
25
- import { getInlineContentSchema } from "../pmUtil.js";
26
- import { getBlockSchema } from "../pmUtil.js";
21
+ import { getBlockInfoWithManualOffset } from "../getBlockInfoFromPos.js";
22
+ import {
23
+ getBlockCache,
24
+ getBlockSchema,
25
+ getInlineContentSchema,
26
+ getStyleSchema,
27
+ } from "../pmUtil.js";
27
28
 
28
29
  /**
29
30
  * Converts an internal (prosemirror) table node contentto a BlockNote Tablecontent
@@ -492,3 +493,197 @@ export function nodeToBlock<
492
493
 
493
494
  return block;
494
495
  }
496
+
497
+ /**
498
+ * Convert a Prosemirror document to a BlockNote document (array of blocks)
499
+ */
500
+ export function docToBlocks<
501
+ BSchema extends BlockSchema,
502
+ I extends InlineContentSchema,
503
+ S extends StyleSchema,
504
+ >(
505
+ doc: Node,
506
+ schema: Schema,
507
+ blockSchema: BSchema = getBlockSchema(schema) as BSchema,
508
+ inlineContentSchema: I = getInlineContentSchema(schema) as I,
509
+ styleSchema: S = getStyleSchema(schema) as S,
510
+ blockCache = getBlockCache(schema),
511
+ ) {
512
+ const blocks: Block<BSchema, I, S>[] = [];
513
+ doc.firstChild!.descendants((node) => {
514
+ blocks.push(
515
+ nodeToBlock(
516
+ node,
517
+ schema,
518
+ blockSchema,
519
+ inlineContentSchema,
520
+ styleSchema,
521
+ blockCache,
522
+ ),
523
+ );
524
+ return false;
525
+ });
526
+ return blocks;
527
+ }
528
+
529
+ /**
530
+ *
531
+ * Parse a Prosemirror Slice into a BlockNote selection. The prosemirror schema looks like this:
532
+ *
533
+ * <blockGroup>
534
+ * <blockContainer> (main content of block)
535
+ * <p, heading, etc.>
536
+ * <blockGroup> (only if blocks has children)
537
+ * <blockContainer> (child block)
538
+ * <p, heading, etc.>
539
+ * </blockContainer>
540
+ * <blockContainer> (child block 2)
541
+ * <p, heading, etc.>
542
+ * </blockContainer>
543
+ * </blockContainer>
544
+ * </blockGroup>
545
+ * </blockGroup>
546
+ *
547
+ */
548
+ export function prosemirrorSliceToSlicedBlocks<
549
+ BSchema extends BlockSchema,
550
+ I extends InlineContentSchema,
551
+ S extends StyleSchema,
552
+ >(
553
+ slice: Slice,
554
+ schema: Schema,
555
+ blockSchema: BSchema = getBlockSchema(schema) as BSchema,
556
+ inlineContentSchema: I = getInlineContentSchema(schema) as I,
557
+ styleSchema: S = getStyleSchema(schema) as S,
558
+ blockCache: WeakMap<Node, Block<BSchema, I, S>> = getBlockCache(schema),
559
+ ): {
560
+ /**
561
+ * The blocks that are included in the selection.
562
+ */
563
+ blocks: Block<BSchema, I, S>[];
564
+ /**
565
+ * If a block was "cut" at the start of the selection, this will be the id of the block that was cut.
566
+ */
567
+ blockCutAtStart: string | undefined;
568
+ /**
569
+ * If a block was "cut" at the end of the selection, this will be the id of the block that was cut.
570
+ */
571
+ blockCutAtEnd: string | undefined;
572
+ } {
573
+ // console.log(JSON.stringify(slice.toJSON()));
574
+ function processNode(
575
+ node: Node,
576
+ openStart: number,
577
+ openEnd: number,
578
+ ): {
579
+ blocks: Block<BSchema, I, S>[];
580
+ blockCutAtStart: string | undefined;
581
+ blockCutAtEnd: string | undefined;
582
+ } {
583
+ if (node.type.name !== "blockGroup") {
584
+ throw new Error("unexpected");
585
+ }
586
+ const blocks: Block<BSchema, I, S>[] = [];
587
+ let blockCutAtStart: string | undefined;
588
+ let blockCutAtEnd: string | undefined;
589
+
590
+ node.forEach((blockContainer, _offset, index) => {
591
+ if (blockContainer.type.name !== "blockContainer") {
592
+ throw new Error("unexpected");
593
+ }
594
+ if (blockContainer.childCount === 0) {
595
+ return;
596
+ }
597
+ if (blockContainer.childCount === 0 || blockContainer.childCount > 2) {
598
+ throw new Error(
599
+ "unexpected, blockContainer.childCount: " + blockContainer.childCount,
600
+ );
601
+ }
602
+
603
+ const isFirstBlock = index === 0;
604
+ const isLastBlock = index === node.childCount - 1;
605
+
606
+ if (blockContainer.firstChild!.type.name === "blockGroup") {
607
+ // this is the parent where a selection starts within one of its children,
608
+ // e.g.:
609
+ // A
610
+ // ├── B
611
+ // selection starts within B, then this blockContainer is A, but we don't care about A
612
+ // so let's descend into B and continue processing
613
+ if (!isFirstBlock) {
614
+ throw new Error("unexpected");
615
+ }
616
+ const ret = processNode(
617
+ blockContainer.firstChild!,
618
+ Math.max(0, openStart - 1),
619
+ isLastBlock ? Math.max(0, openEnd - 1) : 0,
620
+ );
621
+ blockCutAtStart = ret.blockCutAtStart;
622
+ if (isLastBlock) {
623
+ blockCutAtEnd = ret.blockCutAtEnd;
624
+ }
625
+ blocks.push(...ret.blocks);
626
+ return;
627
+ }
628
+
629
+ const block = nodeToBlock(
630
+ blockContainer,
631
+ schema,
632
+ blockSchema,
633
+ inlineContentSchema,
634
+ styleSchema,
635
+ blockCache,
636
+ );
637
+ const childGroup =
638
+ blockContainer.childCount > 1 ? blockContainer.child(1) : undefined;
639
+
640
+ let childBlocks: Block<BSchema, I, S>[] = [];
641
+ if (childGroup) {
642
+ const ret = processNode(
643
+ childGroup,
644
+ 0, // TODO: can this be anything other than 0?
645
+ isLastBlock ? Math.max(0, openEnd - 1) : 0,
646
+ );
647
+ childBlocks = ret.blocks;
648
+ if (isLastBlock) {
649
+ blockCutAtEnd = ret.blockCutAtEnd;
650
+ }
651
+ }
652
+
653
+ if (isLastBlock && !childGroup && openEnd > 1) {
654
+ blockCutAtEnd = block.id;
655
+ }
656
+
657
+ if (isFirstBlock && openStart > 1) {
658
+ blockCutAtStart = block.id;
659
+ }
660
+
661
+ blocks.push({
662
+ ...(block as any),
663
+ children: childBlocks,
664
+ });
665
+ });
666
+
667
+ return { blocks, blockCutAtStart, blockCutAtEnd };
668
+ }
669
+
670
+ if (slice.content.childCount === 0) {
671
+ return {
672
+ blocks: [],
673
+ blockCutAtStart: undefined,
674
+ blockCutAtEnd: undefined,
675
+ };
676
+ }
677
+
678
+ if (slice.content.childCount !== 1) {
679
+ throw new Error(
680
+ "slice must be a single block, did you forget includeParents=true?",
681
+ );
682
+ }
683
+
684
+ return processNode(
685
+ slice.content.firstChild!,
686
+ Math.max(slice.openStart - 1, 0),
687
+ Math.max(slice.openEnd - 1, 0),
688
+ );
689
+ }
package/src/api/pmUtil.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import type { Node, Schema } from "prosemirror-model";
2
- import type { Transaction } from "prosemirror-state";
2
+ import { Transform } from "prosemirror-transform";
3
3
  import type { BlockNoteEditor } from "../editor/BlockNoteEditor.js";
4
+ import { BlockNoteSchema } from "../editor/BlockNoteSchema.js";
4
5
  import type { BlockSchema } from "../schema/blocks/types.js";
5
6
  import type { InlineContentSchema } from "../schema/inlineContent/types.js";
6
7
  import type { StyleSchema } from "../schema/styles/types.js";
7
- import { BlockNoteSchema } from "../editor/BlockNoteSchema.js";
8
8
 
9
- export function getPmSchema(trOrNode: Transaction | Node) {
9
+ export function getPmSchema(trOrNode: Transform | Node) {
10
10
  if ("doc" in trOrNode) {
11
11
  return trOrNode.doc.type.schema;
12
12
  }
@@ -1,15 +1,15 @@
1
+ import type { HighlighterGeneric } from "@shikijs/types";
1
2
  import { InputRule, isTextSelection } from "@tiptap/core";
2
3
  import { TextSelection } from "@tiptap/pm/state";
3
- import { createHighlightPlugin, Parser } from "prosemirror-highlight";
4
+ import { Parser, createHighlightPlugin } from "prosemirror-highlight";
4
5
  import { createParser } from "prosemirror-highlight/shiki";
6
+ import { BlockNoteEditor } from "../../index.js";
5
7
  import {
8
+ PropSchema,
6
9
  createBlockSpecFromStronglyTypedTiptapNode,
7
10
  createStronglyTypedTiptapNode,
8
- PropSchema,
9
11
  } from "../../schema/index.js";
10
12
  import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers.js";
11
- import type { HighlighterGeneric } from "@shikijs/types";
12
- import { BlockNoteEditor } from "../../index.js";
13
13
 
14
14
  export type CodeBlockOptions = {
15
15
  /**
@@ -76,7 +76,7 @@ const CodeBlockContent = createStronglyTypedTiptapNode({
76
76
  name: "codeBlock",
77
77
  content: "inline*",
78
78
  group: "blockContent",
79
- marks: "",
79
+ marks: "insertion deletion modification",
80
80
  code: true,
81
81
  defining: true,
82
82
  addOptions() {
@@ -152,7 +152,7 @@ const CodeBlockContent = createStronglyTypedTiptapNode({
152
152
  // Parse from external HTML.
153
153
  {
154
154
  tag: "pre",
155
- contentElement: "code",
155
+ // contentElement: "code",
156
156
  preserveWhitespace: "full",
157
157
  },
158
158
  ];
@@ -33,7 +33,7 @@ export const createAddFileButton = (
33
33
  // Opens the file toolbar.
34
34
  const addFileButtonClickHandler = () => {
35
35
  editor.transact((tr) =>
36
- tr.setMeta(editor.filePanel!.plugin, {
36
+ tr.setMeta(editor.filePanel!.plugins[0], {
37
37
  block: block,
38
38
  }),
39
39
  );
@@ -1,9 +1,8 @@
1
+ import { Node, mergeAttributes } from "@tiptap/core";
1
2
  import { TableCell } from "@tiptap/extension-table-cell";
2
3
  import { TableHeader } from "@tiptap/extension-table-header";
3
- import { TableRow } from "@tiptap/extension-table-row";
4
4
  import { DOMParser, Fragment, Node as PMNode, Schema } from "prosemirror-model";
5
5
  import { TableView } from "prosemirror-tables";
6
-
7
6
  import { NodeView } from "prosemirror-view";
8
7
  import {
9
8
  createBlockSpecFromStronglyTypedTiptapNode,
@@ -24,6 +23,7 @@ export const TableBlockContent = createStronglyTypedTiptapNode({
24
23
  group: "blockContent",
25
24
  tableRole: "table",
26
25
 
26
+ marks: "deletion insertion modification",
27
27
  isolating: true,
28
28
 
29
29
  parseHTML() {
@@ -152,6 +152,36 @@ const TableParagraph = createStronglyTypedTiptapNode({
152
152
  });
153
153
 
154
154
  /**
155
+ * This extension allows you to create table rows.
156
+ * @see https://www.tiptap.dev/api/nodes/table-row
157
+ */
158
+ export const TableRow = Node.create<{ HTMLAttributes: Record<string, any> }>({
159
+ name: "tableRow",
160
+
161
+ addOptions() {
162
+ return {
163
+ HTMLAttributes: {},
164
+ };
165
+ },
166
+
167
+ content: "(tableCell | tableHeader)+",
168
+
169
+ tableRole: "row",
170
+ marks: "deletion insertion modification",
171
+ parseHTML() {
172
+ return [{ tag: "tr" }];
173
+ },
174
+
175
+ renderHTML({ HTMLAttributes }) {
176
+ return [
177
+ "tr",
178
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
179
+ 0,
180
+ ];
181
+ },
182
+ });
183
+
184
+ /*
155
185
  * This will flatten a node's content to fit into a table cell's paragraph.
156
186
  */
157
187
  function parseTableContent(node: HTMLElement, schema: Schema) {
@@ -152,6 +152,11 @@ NESTED BLOCKS
152
152
 
153
153
  .bn-block-outer:not([data-prev-type])
154
154
  > .bn-block
155
+ > .bn-block-content[data-content-type="heading"],
156
+ .bn-block-outer:not([data-prev-type])
157
+ > .bn-block
158
+ > div[data-type="modification"]
159
+ > div[data-type="modification"]
155
160
  > .bn-block-content[data-content-type="heading"] {
156
161
  font-size: var(--level);
157
162
  font-weight: bold;
@@ -195,6 +200,10 @@ NESTED BLOCKS
195
200
 
196
201
  .bn-block-outer:not([data-prev-type])
197
202
  > .bn-block
203
+ > .bn-block-content[data-content-type="numberedListItem"]::before,
204
+ .bn-block-outer:not([data-prev-type])
205
+ > .bn-block
206
+ > div[data-type="modification"]
198
207
  > .bn-block-content[data-content-type="numberedListItem"]::before {
199
208
  content: var(--index) ".";
200
209
  }
@@ -239,6 +248,10 @@ NESTED BLOCKS
239
248
 
240
249
  .bn-block-outer:not([data-prev-type])
241
250
  > .bn-block
251
+ > .bn-block-content[data-content-type="bulletListItem"]::before,
252
+ .bn-block-outer:not([data-prev-type])
253
+ > .bn-block
254
+ > div[data-type="modification"]
242
255
  > .bn-block-content[data-content-type="bulletListItem"]::before {
243
256
  content: "•";
244
257
  }
@@ -256,6 +269,12 @@ NESTED BLOCKS
256
269
  ~ .bn-block-group
257
270
  > .bn-block-outer:not([data-prev-type])
258
271
  > .bn-block
272
+ > .bn-block-content[data-content-type="bulletListItem"]::before,
273
+ [data-content-type="bulletListItem"]
274
+ ~ .bn-block-group
275
+ > .bn-block-outer:not([data-prev-type])
276
+ > .bn-block
277
+ > div[data-type="modification"]
259
278
  > .bn-block-content[data-content-type="bulletListItem"]::before {
260
279
  content: "◦";
261
280
  }
@@ -277,6 +296,14 @@ NESTED BLOCKS
277
296
  ~ .bn-block-group
278
297
  > .bn-block-outer:not([data-prev-type])
279
298
  > .bn-block
299
+ > .bn-block-content[data-content-type="bulletListItem"]::before,
300
+ [data-content-type="bulletListItem"]
301
+ ~ .bn-block-group
302
+ [data-content-type="bulletListItem"]
303
+ ~ .bn-block-group
304
+ > .bn-block-outer:not([data-prev-type])
305
+ > .bn-block
306
+ > div[data-type="modification"]
280
307
  > .bn-block-content[data-content-type="bulletListItem"]::before {
281
308
  content: "▪";
282
309
  }
@@ -442,7 +469,6 @@ NESTED BLOCKS
442
469
  position: absolute;
443
470
  font-style: italic;
444
471
  }
445
-
446
472
  /* TODO: should this be here? */
447
473
 
448
474
  /* TEXT COLORS */
@@ -75,6 +75,13 @@ it("adds id attribute when requested", async () => {
75
75
  );
76
76
  });
77
77
 
78
+ it("updates block", () => {
79
+ const editor = BlockNoteEditor.create();
80
+ editor.updateBlock(editor.document[0], {
81
+ content: "hello",
82
+ });
83
+ });
84
+
78
85
  it("block prop types", () => {
79
86
  // this test checks whether the block props are correctly typed in typescript
80
87
  const editor = BlockNoteEditor.create();
@@ -33,12 +33,13 @@ import {
33
33
  import { insertContentAt } from "../api/blockManipulation/insertContentAt.js";
34
34
  import {
35
35
  getSelection,
36
+ getSelectionCutBlocks,
36
37
  setSelection,
37
38
  } from "../api/blockManipulation/selections/selection.js";
38
39
  import {
39
40
  getTextCursorPosition,
40
41
  setTextCursorPosition,
41
- } from "../api/blockManipulation/selections/textCursorPosition/textCursorPosition.js";
42
+ } from "../api/blockManipulation/selections/textCursorPosition.js";
42
43
  import { createExternalHTMLExporter } from "../api/exporters/html/externalHTMLExporter.js";
43
44
  import { blocksToMarkdown } from "../api/exporters/markdown/markdownExporter.js";
44
45
  import { HTMLToBlocks } from "../api/parsers/html/parseHTML.js";
@@ -93,6 +94,7 @@ import {
93
94
  import { Dictionary } from "../i18n/dictionary.js";
94
95
  import { en } from "../i18n/locales/index.js";
95
96
 
97
+ import { redo, undo } from "@tiptap/pm/history";
96
98
  import {
97
99
  TextSelection,
98
100
  type Command,
@@ -101,11 +103,10 @@ import {
101
103
  } from "@tiptap/pm/state";
102
104
  import { dropCursor } from "prosemirror-dropcursor";
103
105
  import { EditorView } from "prosemirror-view";
104
- import { undoCommand, redoCommand, ySyncPluginKey } from "y-prosemirror";
105
- import { undo, redo } from "@tiptap/pm/history";
106
+ import { redoCommand, undoCommand, ySyncPluginKey } from "y-prosemirror";
106
107
  import { createInternalHTMLSerializer } from "../api/exporters/html/internalHTMLSerializer.js";
107
108
  import { inlineContentToNodes } from "../api/nodeConversions/blockToNode.js";
108
- import { nodeToBlock } from "../api/nodeConversions/nodeToBlock.js";
109
+ import { docToBlocks } from "../api/nodeConversions/nodeToBlock.js";
109
110
  import {
110
111
  BlocksChanged,
111
112
  getBlocksChangedByTransaction,
@@ -113,20 +114,26 @@ import {
113
114
  import { nestedListsToBlockNoteStructure } from "../api/parsers/html/util/nestedLists.js";
114
115
  import { CodeBlockOptions } from "../blocks/CodeBlockContent/CodeBlockContent.js";
115
116
  import type { ThreadStore, User } from "../comments/index.js";
116
- import "../style.css";
117
+ import type { CursorPlugin } from "../extensions/Collaboration/CursorPlugin.js";
118
+ import type { ForkYDocPlugin } from "../extensions/Collaboration/ForkYDocPlugin.js";
117
119
  import { EventEmitter } from "../util/EventEmitter.js";
118
- import { CursorPlugin } from "../extensions/Collaboration/CursorPlugin.js";
120
+ import { BlockNoteExtension } from "./BlockNoteExtension.js";
121
+
122
+ import "../style.css";
119
123
 
124
+ /**
125
+ * A factory function that returns a BlockNoteExtension
126
+ * This is useful so we can create extensions that require an editor instance
127
+ * in the constructor
128
+ */
120
129
  export type BlockNoteExtensionFactory = (
121
130
  editor: BlockNoteEditor<any, any, any>,
122
131
  ) => BlockNoteExtension;
123
132
 
124
- export type BlockNoteExtension =
125
- | AnyExtension
126
- | {
127
- plugin: Plugin;
128
- priority?: number;
129
- };
133
+ /**
134
+ * We support Tiptap extensions and BlockNoteExtension based extensions
135
+ */
136
+ export type SupportedExtension = AnyExtension | BlockNoteExtension;
130
137
 
131
138
  export type BlockCache<
132
139
  BSchema extends BlockSchema = any,
@@ -369,10 +376,17 @@ export type BlockNoteEditorOptions<
369
376
  _tiptapOptions: Partial<EditorOptions>;
370
377
 
371
378
  /**
372
- * (experimental) add extra prosemirror plugins or tiptap extensions to the editor
379
+ * (experimental) add extra extensions to the editor
380
+ *
381
+ * @deprecated, should use `extensions` instead
373
382
  */
374
383
  _extensions: Record<string, BlockNoteExtension | BlockNoteExtensionFactory>;
375
384
 
385
+ /**
386
+ * Register
387
+ */
388
+ extensions: Array<BlockNoteExtension | BlockNoteExtensionFactory>;
389
+
376
390
  /**
377
391
  * Boolean indicating whether the editor is in headless mode.
378
392
  * Headless mode means we can use features like importing / exporting blocks,
@@ -404,7 +418,7 @@ export class BlockNoteEditor<
404
418
  /**
405
419
  * extensions that are added to the editor, can be tiptap extensions or prosemirror plugins
406
420
  */
407
- public readonly extensions: Record<string, BlockNoteExtension> = {};
421
+ public extensions: Record<string, SupportedExtension> = {};
408
422
 
409
423
  /**
410
424
  * Boolean indicating whether the editor is in headless mode.
@@ -473,8 +487,10 @@ export class BlockNoteEditor<
473
487
 
474
488
  private readonly showSelectionPlugin: ShowSelectionPlugin;
475
489
 
476
- private readonly cursorPlugin: CursorPlugin;
477
-
490
+ /**
491
+ * The plugin for forking a document, only defined if in collaboration mode
492
+ */
493
+ public readonly forkYDocPlugin?: ForkYDocPlugin;
478
494
  /**
479
495
  * The `uploadFile` method is what the editor uses when files need to be uploaded (for example when selecting an image to upload).
480
496
  * This method should set when creating the editor as this is application-specific.
@@ -609,6 +625,16 @@ export class BlockNoteEditor<
609
625
  });
610
626
 
611
627
  // add extensions from options
628
+ for (let ext of newOptions.extensions || []) {
629
+ if (typeof ext === "function") {
630
+ // factory
631
+ ext = ext(this);
632
+ }
633
+ const key = (ext.constructor as any).name();
634
+ this.extensions[key] = ext;
635
+ }
636
+
637
+ // (when passed in via the deprecated `_extensions` option)
612
638
  Object.entries(newOptions._extensions || {}).forEach(([key, ext]) => {
613
639
  if (typeof ext === "function") {
614
640
  // factory
@@ -625,7 +651,7 @@ export class BlockNoteEditor<
625
651
  this.tableHandles = this.extensions["tableHandles"] as any;
626
652
  this.comments = this.extensions["comments"] as any;
627
653
  this.showSelectionPlugin = this.extensions["showSelection"] as any;
628
- this.cursorPlugin = this.extensions["yCursorPlugin"] as any;
654
+ this.forkYDocPlugin = this.extensions["forkYDocPlugin"] as any;
629
655
 
630
656
  if (newOptions.uploadFile) {
631
657
  const uploadFile = newOptions.uploadFile;
@@ -691,20 +717,19 @@ export class BlockNoteEditor<
691
717
  return ext;
692
718
  }
693
719
 
694
- if (!ext.plugin) {
695
- throw new Error(
696
- "Extension should either be a TipTap extension or a ProseMirror plugin in a plugin property",
697
- );
720
+ if (ext instanceof BlockNoteExtension && !ext.plugins.length) {
721
+ return undefined;
698
722
  }
699
723
 
700
724
  // "blocknote" extensions (prosemirror plugins)
701
725
  return Extension.create({
702
726
  name: key,
703
727
  priority: ext.priority,
704
- addProseMirrorPlugins: () => [ext.plugin],
728
+ addProseMirrorPlugins: () => ext.plugins,
705
729
  });
706
730
  }),
707
- ];
731
+ ].filter((ext): ext is Extension => ext !== undefined);
732
+
708
733
  const tiptapOptions: BlockNoteTipTapEditorOptions = {
709
734
  ...blockNoteTipTapOptions,
710
735
  ...newOptions._tiptapOptions,
@@ -865,6 +890,26 @@ export class BlockNoteEditor<
865
890
  }
866
891
  }
867
892
 
893
+ // TO DISCUSS
894
+ /**
895
+ * Shorthand to get a typed extension from the editor, by
896
+ * just passing in the extension class.
897
+ *
898
+ * @param ext - The extension class to get
899
+ * @param key - optional, the key of the extension in the extensions object (defaults to the extension name)
900
+ * @returns The extension instance
901
+ */
902
+ public extension<T extends BlockNoteExtension>(
903
+ ext: { new (...args: any[]): T } & typeof BlockNoteExtension,
904
+ key = ext.name(),
905
+ ): T {
906
+ const extension = this.extensions[key] as T;
907
+ if (!extension) {
908
+ throw new Error(`Extension ${key} not found`);
909
+ }
910
+ return extension;
911
+ }
912
+
868
913
  /**
869
914
  * Mount the editor to a parent DOM element. Call mount(undefined) to clean up
870
915
  *
@@ -946,15 +991,7 @@ export class BlockNoteEditor<
946
991
  */
947
992
  public get document(): Block<BSchema, ISchema, SSchema>[] {
948
993
  return this.transact((tr) => {
949
- const blocks: Block<BSchema, ISchema, SSchema>[] = [];
950
-
951
- tr.doc.firstChild!.descendants((node) => {
952
- blocks.push(nodeToBlock(node, this.pmSchema));
953
-
954
- return false;
955
- });
956
-
957
- return blocks;
994
+ return docToBlocks(tr.doc, this.pmSchema);
958
995
  });
959
996
  }
960
997
 
@@ -1099,12 +1136,26 @@ export class BlockNoteEditor<
1099
1136
  }
1100
1137
 
1101
1138
  /**
1102
- * Gets a snapshot of the current selection.
1139
+ * Gets a snapshot of the current selection. This contains all blocks (included nested blocks)
1140
+ * that the selection spans across.
1141
+ *
1142
+ * If the selection starts / ends halfway through a block, the returned data will contain the entire block.
1103
1143
  */
1104
1144
  public getSelection(): Selection<BSchema, ISchema, SSchema> | undefined {
1105
1145
  return this.transact((tr) => getSelection(tr));
1106
1146
  }
1107
1147
 
1148
+ /**
1149
+ * Gets a snapshot of the current selection. This contains all blocks (included nested blocks)
1150
+ * that the selection spans across.
1151
+ *
1152
+ * If the selection starts / ends halfway through a block, the returned block will be
1153
+ * only the part of the block that is included in the selection.
1154
+ */
1155
+ public getSelectionCutBlocks() {
1156
+ return this.transact((tr) => getSelectionCutBlocks(tr));
1157
+ }
1158
+
1108
1159
  /**
1109
1160
  * Sets the selection to a range of blocks.
1110
1161
  * @param startBlock The identifier of the block that should be the start of the selection.
@@ -1500,7 +1551,7 @@ export class BlockNoteEditor<
1500
1551
  );
1501
1552
  }
1502
1553
 
1503
- this.cursorPlugin.updateUser(user);
1554
+ (this.extensions["yCursorPlugin"] as CursorPlugin).updateUser(user);
1504
1555
  }
1505
1556
 
1506
1557
  /**
@@ -1595,8 +1646,8 @@ export class BlockNoteEditor<
1595
1646
  if (!this.prosemirrorView) {
1596
1647
  return undefined;
1597
1648
  }
1598
- const state = this.prosemirrorView?.state;
1599
- const { selection } = state;
1649
+
1650
+ const { selection } = this.prosemirrorState;
1600
1651
 
1601
1652
  // support for CellSelections
1602
1653
  const { ranges } = selection;
@@ -1641,7 +1692,7 @@ export class BlockNoteEditor<
1641
1692
  if (pluginState?.deleteTriggerCharacter) {
1642
1693
  tr.insertText(triggerCharacter);
1643
1694
  }
1644
- tr.scrollIntoView().setMeta(this.suggestionMenus.plugin, {
1695
+ tr.scrollIntoView().setMeta(this.suggestionMenus.plugins[0], {
1645
1696
  triggerCharacter: triggerCharacter,
1646
1697
  deleteTriggerCharacter: pluginState?.deleteTriggerCharacter || false,
1647
1698
  ignoreQueryLength: pluginState?.ignoreQueryLength || false,