@blocknote/core 0.24.1 → 0.25.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 (149) hide show
  1. package/dist/blocknote.cjs +12 -0
  2. package/dist/blocknote.cjs.map +1 -0
  3. package/dist/blocknote.js +5028 -3444
  4. package/dist/blocknote.js.map +1 -1
  5. package/dist/comments.cjs +2 -0
  6. package/dist/comments.cjs.map +1 -0
  7. package/dist/comments.js +593 -0
  8. package/dist/comments.js.map +1 -0
  9. package/dist/style.css +1 -1
  10. package/dist/tsconfig.tsbuildinfo +1 -1
  11. package/dist/webpack-stats.json +1 -1
  12. package/package.json +39 -26
  13. package/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap +1022 -378
  14. package/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap +730 -270
  15. package/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap +3100 -1260
  16. package/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap +438 -162
  17. package/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap +1168 -432
  18. package/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap +930 -378
  19. package/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap +2485 -1015
  20. package/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts +28 -1
  21. package/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +1 -1
  22. package/src/api/blockManipulation/selections/__snapshots__/selection.test.ts.snap +292 -108
  23. package/src/api/blockManipulation/setupTestEnv.ts +14 -1
  24. package/src/api/blockManipulation/tables/tables.test.ts +1987 -0
  25. package/src/api/blockManipulation/tables/tables.ts +887 -0
  26. package/src/api/clipboard/__snapshots__/external/pasteEndOfParagraph.html +66 -24
  27. package/src/api/clipboard/__snapshots__/external/pasteEndOfParagraphText.html +66 -24
  28. package/src/api/clipboard/__snapshots__/external/pasteImage.html +66 -24
  29. package/src/api/clipboard/__snapshots__/external/pasteParagraphInCustomBlock.html +66 -24
  30. package/src/api/clipboard/__snapshots__/external/pasteTable.html +132 -48
  31. package/src/api/clipboard/__snapshots__/external/pasteTableInExistingTable.html +136 -44
  32. package/src/api/clipboard/fromClipboard/handleFileInsertion.ts +36 -14
  33. package/src/api/clipboard/toClipboard/copyExtension.ts +2 -3
  34. package/src/api/exporters/html/__snapshots__/table/headerCols/external.html +1 -0
  35. package/src/api/exporters/html/__snapshots__/table/headerCols/internal.html +1 -0
  36. package/src/api/exporters/html/__snapshots__/table/headerRows/external.html +1 -0
  37. package/src/api/exporters/html/__snapshots__/table/headerRows/internal.html +1 -0
  38. package/src/api/exporters/html/__snapshots__/table/headersRows/external.html +1 -0
  39. package/src/api/exporters/html/__snapshots__/table/headersRows/internal.html +1 -0
  40. package/src/api/exporters/html/__snapshots__/table/mixedCellColors/external.html +1 -0
  41. package/src/api/exporters/html/__snapshots__/table/mixedCellColors/internal.html +1 -0
  42. package/src/api/exporters/html/__snapshots__/table/mixedRowspansAndColspans/external.html +1 -0
  43. package/src/api/exporters/html/__snapshots__/table/mixedRowspansAndColspans/internal.html +1 -0
  44. package/src/api/exporters/markdown/__snapshots__/table/headerCols/markdown.md +4 -0
  45. package/src/api/exporters/markdown/__snapshots__/table/headerRows/markdown.md +4 -0
  46. package/src/api/exporters/markdown/__snapshots__/table/mixedCellColors/markdown.md +5 -0
  47. package/src/api/exporters/markdown/__snapshots__/table/mixedRowspansAndColspans/markdown.md +5 -0
  48. package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +985 -20
  49. package/src/api/nodeConversions/blockToNode.ts +63 -20
  50. package/src/api/nodeConversions/nodeToBlock.ts +75 -13
  51. package/src/api/parsers/html/__snapshots__/parse-notion-html.json +145 -54
  52. package/src/api/testUtil/cases/defaultSchema.ts +782 -9
  53. package/src/api/testUtil/partialBlockTestUtil.ts +39 -4
  54. package/src/blocks/TableBlockContent/TableBlockContent.ts +11 -5
  55. package/src/blocks/defaultBlockTypeGuards.ts +8 -0
  56. package/src/comments/index.ts +9 -0
  57. package/src/comments/models/User.ts +8 -0
  58. package/src/comments/threadstore/DefaultThreadStoreAuth.ts +106 -0
  59. package/src/comments/threadstore/ThreadStore.ts +134 -0
  60. package/src/comments/threadstore/ThreadStoreAuth.ts +13 -0
  61. package/src/comments/threadstore/TipTapThreadStore.ts +292 -0
  62. package/src/comments/threadstore/yjs/RESTYjsThreadStore.ts +144 -0
  63. package/src/comments/threadstore/yjs/YjsThreadStore.test.ts +294 -0
  64. package/src/comments/threadstore/yjs/YjsThreadStore.ts +340 -0
  65. package/src/comments/threadstore/yjs/YjsThreadStoreBase.ts +48 -0
  66. package/src/comments/threadstore/yjs/yjsHelpers.ts +121 -0
  67. package/src/comments/types.ts +117 -0
  68. package/src/editor/Block.css +16 -8
  69. package/src/editor/BlockNoteEditor.ts +269 -92
  70. package/src/editor/BlockNoteExtensions.ts +24 -1
  71. package/src/editor/BlockNoteTipTapEditor.ts +5 -1
  72. package/src/editor/editor.css +17 -0
  73. package/src/extensions/BackgroundColor/BackgroundColorExtension.ts +1 -1
  74. package/src/extensions/Comments/CommentMark.ts +61 -0
  75. package/src/extensions/Comments/CommentsPlugin.ts +301 -0
  76. package/src/extensions/Comments/userstore/UserStore.ts +72 -0
  77. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +16 -10
  78. package/src/extensions/LinkToolbar/LinkToolbarPlugin.ts +3 -3
  79. package/src/extensions/ShowSelection/ShowSelectionPlugin.ts +52 -0
  80. package/src/extensions/SideMenu/SideMenuPlugin.ts +22 -9
  81. package/src/extensions/TableHandles/TableHandlesPlugin.ts +409 -57
  82. package/src/extensions/TextAlignment/TextAlignmentExtension.ts +2 -0
  83. package/src/extensions/TextColor/TextColorExtension.ts +1 -1
  84. package/src/extensions/UniqueID/UniqueID.ts +8 -3
  85. package/src/i18n/locales/ar.ts +23 -0
  86. package/src/i18n/locales/de.ts +15 -0
  87. package/src/i18n/locales/en.ts +25 -1
  88. package/src/i18n/locales/es.ts +16 -1
  89. package/src/i18n/locales/fr.ts +23 -0
  90. package/src/i18n/locales/hr.ts +18 -0
  91. package/src/i18n/locales/index.ts +1 -0
  92. package/src/i18n/locales/is.ts +24 -1
  93. package/src/i18n/locales/it.ts +21 -0
  94. package/src/i18n/locales/ja.ts +23 -0
  95. package/src/i18n/locales/ko.ts +23 -0
  96. package/src/i18n/locales/nl.ts +23 -0
  97. package/src/i18n/locales/no.ts +346 -0
  98. package/src/i18n/locales/pl.ts +23 -0
  99. package/src/i18n/locales/pt.ts +23 -0
  100. package/src/i18n/locales/ru.ts +23 -0
  101. package/src/i18n/locales/uk.ts +23 -0
  102. package/src/i18n/locales/vi.ts +23 -0
  103. package/src/i18n/locales/zh.ts +23 -0
  104. package/src/index.ts +6 -4
  105. package/src/schema/blocks/types.ts +32 -2
  106. package/src/util/browser.ts +1 -1
  107. package/src/util/table.ts +107 -0
  108. package/types/src/api/blockManipulation/tables/tables.d.ts +343 -0
  109. package/types/src/api/blockManipulation/tables/tables.test.d.ts +1 -0
  110. package/types/src/api/clipboard/toClipboard/copyExtension.d.ts +1 -1
  111. package/types/src/blocks/TableBlockContent/TableBlockContent.d.ts +1 -2
  112. package/types/src/blocks/defaultBlockTypeGuards.d.ts +3 -0
  113. package/types/src/comments/index.d.ts +9 -0
  114. package/types/src/comments/models/User.d.ts +8 -0
  115. package/types/src/comments/threadstore/DefaultThreadStoreAuth.d.ts +47 -0
  116. package/types/src/comments/threadstore/ThreadStore.d.ts +121 -0
  117. package/types/src/comments/threadstore/ThreadStoreAuth.d.ts +12 -0
  118. package/types/src/comments/threadstore/TipTapThreadStore.d.ts +97 -0
  119. package/types/src/comments/threadstore/yjs/RESTYjsThreadStore.d.ts +83 -0
  120. package/types/src/comments/threadstore/yjs/YjsThreadStore.d.ts +79 -0
  121. package/types/src/comments/threadstore/yjs/YjsThreadStore.test.d.ts +1 -0
  122. package/types/src/comments/threadstore/yjs/YjsThreadStoreBase.d.ts +15 -0
  123. package/types/src/comments/threadstore/yjs/yjsHelpers.d.ts +13 -0
  124. package/types/src/comments/types.d.ts +109 -0
  125. package/types/src/editor/BlockNoteEditor.d.ts +146 -66
  126. package/types/src/editor/BlockNoteExtensions.d.ts +4 -0
  127. package/types/src/extensions/Collaboration/createCollaborationExtensions.d.ts +1 -1
  128. package/types/src/extensions/Comments/CommentMark.d.ts +2 -0
  129. package/types/src/extensions/Comments/CommentsPlugin.d.ts +49 -0
  130. package/types/src/extensions/Comments/userstore/UserStore.d.ts +31 -0
  131. package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +1 -1
  132. package/types/src/extensions/ShowSelection/ShowSelectionPlugin.d.ts +15 -0
  133. package/types/src/extensions/SideMenu/SideMenuPlugin.d.ts +1 -0
  134. package/types/src/extensions/TableHandles/TableHandlesPlugin.d.ts +66 -1
  135. package/types/src/i18n/locales/de.d.ts +15 -0
  136. package/types/src/i18n/locales/en.d.ts +20 -0
  137. package/types/src/i18n/locales/es.d.ts +15 -0
  138. package/types/src/i18n/locales/hr.d.ts +18 -0
  139. package/types/src/i18n/locales/index.d.ts +1 -0
  140. package/types/src/i18n/locales/it.d.ts +21 -0
  141. package/types/src/i18n/locales/no.d.ts +2 -0
  142. package/types/src/index.d.ts +5 -4
  143. package/types/src/pm-nodes/BlockContainer.d.ts +2 -2
  144. package/types/src/pm-nodes/BlockGroup.d.ts +2 -2
  145. package/types/src/schema/blocks/types.d.ts +23 -2
  146. package/types/src/util/browser.d.ts +1 -1
  147. package/types/src/util/table.d.ts +12 -0
  148. package/dist/blocknote.umd.cjs +0 -11
  149. package/dist/blocknote.umd.cjs.map +0 -1
@@ -1,8 +1,35 @@
1
1
  import { Plugin, PluginKey, PluginView } from "prosemirror-state";
2
+ import {
3
+ addColumnAfter,
4
+ addColumnBefore,
5
+ addRowAfter,
6
+ addRowBefore,
7
+ CellSelection,
8
+ deleteColumn,
9
+ deleteRow,
10
+ mergeCells,
11
+ splitCell,
12
+ } from "prosemirror-tables";
2
13
  import { Decoration, DecorationSet, EditorView } from "prosemirror-view";
14
+ import {
15
+ addRowsOrColumns,
16
+ areInSameColumn,
17
+ canColumnBeDraggedInto,
18
+ canRowBeDraggedInto,
19
+ cropEmptyRowsOrColumns,
20
+ getCellsAtColumnHandle,
21
+ getCellsAtRowHandle,
22
+ getDimensionsOfTable,
23
+ moveColumn,
24
+ moveRow,
25
+ RelativeCellIndices,
26
+ } from "../../api/blockManipulation/tables/tables.js";
3
27
  import { nodeToBlock } from "../../api/nodeConversions/nodeToBlock.js";
4
28
  import { getNodeById } from "../../api/nodeUtil.js";
5
- import { checkBlockIsDefaultType } from "../../blocks/defaultBlockTypeGuards.js";
29
+ import {
30
+ checkBlockIsDefaultType,
31
+ isTableCellSelection,
32
+ } from "../../blocks/defaultBlockTypeGuards.js";
6
33
  import { DefaultBlockSchema } from "../../blocks/defaultBlocks.js";
7
34
  import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
8
35
  import {
@@ -16,6 +43,7 @@ import { getDraggableBlockFromElement } from "../getDraggableBlockFromElement.js
16
43
 
17
44
  let dragImageElement: HTMLElement | undefined;
18
45
 
46
+ // TODO consider switching this to jotai, it is a bit messy and noisy
19
47
  export type TableHandlesState<
20
48
  I extends InlineContentSchema,
21
49
  S extends StyleSchema
@@ -159,7 +187,10 @@ export class TableHandlesView<
159
187
  "dragover",
160
188
  this.dragOverHandler as EventListener
161
189
  );
162
- pmView.root.addEventListener("drop", this.dropHandler as EventListener);
190
+ pmView.root.addEventListener(
191
+ "drop",
192
+ this.dropHandler as unknown as EventListener
193
+ );
163
194
  }
164
195
 
165
196
  viewMousedownHandler = () => {
@@ -284,11 +315,11 @@ export class TableHandlesView<
284
315
  referencePosTable: tableRect,
285
316
  block: tableBlock,
286
317
  widgetContainer,
287
- colIndex: hideHandles ? undefined : this.state!.colIndex,
288
- rowIndex: hideHandles ? undefined : this.state!.rowIndex,
318
+ colIndex: hideHandles ? undefined : this.state?.colIndex,
319
+ rowIndex: hideHandles ? undefined : this.state?.rowIndex,
289
320
  referencePosCell: hideHandles
290
321
  ? undefined
291
- : this.state!.referencePosCell,
322
+ : this.state?.referencePosCell,
292
323
  };
293
324
  } else {
294
325
  const colIndex = getChildIndex(target.domNode);
@@ -427,7 +458,7 @@ export class TableHandlesView<
427
458
  dropHandler = (event: DragEvent) => {
428
459
  this.mouseState = "up";
429
460
  if (this.state === undefined || this.state.draggingState === undefined) {
430
- return;
461
+ return false;
431
462
  }
432
463
 
433
464
  if (
@@ -443,33 +474,64 @@ export class TableHandlesView<
443
474
 
444
475
  const { draggingState, colIndex, rowIndex } = this.state;
445
476
 
446
- const rows = this.state.block.content.rows;
477
+ const columnWidths = this.state.block.content.columnWidths;
447
478
 
448
479
  if (draggingState.draggedCellOrientation === "row") {
449
- const rowToMove = rows[draggingState.originalIndex];
450
- rows.splice(draggingState.originalIndex, 1);
451
- rows.splice(rowIndex, 0, rowToMove);
480
+ if (
481
+ !canRowBeDraggedInto(
482
+ this.state.block,
483
+ draggingState.originalIndex,
484
+ rowIndex
485
+ )
486
+ ) {
487
+ // If the target row is invalid, don't move the row
488
+ return false;
489
+ }
490
+ const newTable = moveRow(
491
+ this.state.block,
492
+ draggingState.originalIndex,
493
+ rowIndex
494
+ );
495
+ this.editor.updateBlock(this.state.block, {
496
+ type: "table",
497
+ content: {
498
+ ...this.state.block.content,
499
+ rows: newTable as any,
500
+ },
501
+ });
452
502
  } else {
453
- const cellsToMove = rows.map(
454
- (row) => row.cells[draggingState.originalIndex]
503
+ if (
504
+ !canColumnBeDraggedInto(
505
+ this.state.block,
506
+ draggingState.originalIndex,
507
+ colIndex
508
+ )
509
+ ) {
510
+ // If the target column is invalid, don't move the column
511
+ return false;
512
+ }
513
+ const newTable = moveColumn(
514
+ this.state.block,
515
+ draggingState.originalIndex,
516
+ colIndex
455
517
  );
456
- rows.forEach((row, rowIndex) => {
457
- row.cells.splice(draggingState.originalIndex, 1);
458
- row.cells.splice(colIndex, 0, cellsToMove[rowIndex]);
518
+ const [columnWidth] = columnWidths.splice(draggingState.originalIndex, 1);
519
+ columnWidths.splice(colIndex, 0, columnWidth);
520
+ this.editor.updateBlock(this.state.block, {
521
+ type: "table",
522
+ content: {
523
+ ...this.state.block.content,
524
+ columnWidths,
525
+ rows: newTable as any,
526
+ },
459
527
  });
460
528
  }
461
529
 
462
- this.editor.updateBlock(this.state.block, {
463
- type: "table",
464
- content: {
465
- type: "tableContent",
466
- rows: rows,
467
- },
468
- });
469
-
470
530
  // Have to reset text cursor position to the block as `updateBlock` moves
471
531
  // the existing selection out of the block.
472
532
  this.editor.setTextCursorPosition(this.state.block.id);
533
+
534
+ return true;
473
535
  };
474
536
  // Updates drag handles when the table is modified or removed.
475
537
  update() {
@@ -488,8 +550,9 @@ export class TableHandlesView<
488
550
  return;
489
551
  }
490
552
 
491
- const rowCount = this.state.block.content.rows.length;
492
- const colCount = this.state.block.content.rows[0].cells.length;
553
+ const { height: rowCount, width: colCount } = getDimensionsOfTable(
554
+ this.state.block
555
+ );
493
556
 
494
557
  if (
495
558
  this.state.rowIndex !== undefined &&
@@ -520,8 +583,12 @@ export class TableHandlesView<
520
583
  ) {
521
584
  const row = tableBody.children[this.state.rowIndex];
522
585
  const cell = row.children[this.state.colIndex];
523
-
524
- this.state.referencePosCell = cell.getBoundingClientRect();
586
+ if (cell) {
587
+ this.state.referencePosCell = cell.getBoundingClientRect();
588
+ } else {
589
+ this.state.rowIndex = undefined;
590
+ this.state.colIndex = undefined;
591
+ }
525
592
  }
526
593
  this.state.referencePosTable = tableBody.getBoundingClientRect();
527
594
 
@@ -538,7 +605,7 @@ export class TableHandlesView<
538
605
  );
539
606
  this.pmView.root.removeEventListener(
540
607
  "drop",
541
- this.dropHandler as EventListener
608
+ this.dropHandler as unknown as EventListener
542
609
  );
543
610
  }
544
611
  }
@@ -594,35 +661,53 @@ export class TableHandlesProsemirrorPlugin<
594
661
 
595
662
  if (newIndex === this.view.state.draggingState.originalIndex) {
596
663
  return DecorationSet.create(state.doc, decorations);
664
+ } else if (
665
+ this.view.state.draggingState.draggedCellOrientation === "row" &&
666
+ !canRowBeDraggedInto(
667
+ this.view.state.block,
668
+ this.view.state.draggingState.originalIndex,
669
+ newIndex
670
+ )
671
+ ) {
672
+ return DecorationSet.create(state.doc, decorations);
673
+ } else if (
674
+ this.view.state.draggingState.draggedCellOrientation === "col" &&
675
+ !canColumnBeDraggedInto(
676
+ this.view.state.block,
677
+ this.view.state.draggingState.originalIndex,
678
+ newIndex
679
+ )
680
+ ) {
681
+ return DecorationSet.create(state.doc, decorations);
597
682
  }
598
683
 
599
684
  // Gets the table to show the drop cursor in.
600
685
  const tableResolvedPos = state.doc.resolve(this.view.tablePos + 1);
601
- const tableNode = tableResolvedPos.node();
686
+ const originalIndex = this.view.state.draggingState.originalIndex;
602
687
 
603
688
  if (this.view.state.draggingState.draggedCellOrientation === "row") {
604
- // Gets the row at the new index.
605
- const rowResolvedPos = state.doc.resolve(
606
- tableResolvedPos.posAtIndex(newIndex) + 1
689
+ const cellsInRow = getCellsAtRowHandle(
690
+ this.view.state.block,
691
+ newIndex
607
692
  );
608
- const rowNode = rowResolvedPos.node();
609
693
 
610
- // Iterates over all cells in the row.
611
- for (let i = 0; i < rowNode.childCount; i++) {
612
- // Gets each cell in the row.
694
+ cellsInRow.forEach(({ row, col }) => {
695
+ // Gets each row in the table.
696
+ const rowResolvedPos = state.doc.resolve(
697
+ tableResolvedPos.posAtIndex(row) + 1
698
+ );
699
+
700
+ // Gets the cell within the row.
613
701
  const cellResolvedPos = state.doc.resolve(
614
- rowResolvedPos.posAtIndex(i) + 1
702
+ rowResolvedPos.posAtIndex(col) + 1
615
703
  );
616
704
  const cellNode = cellResolvedPos.node();
617
-
618
705
  // Creates a decoration at the start or end of each cell,
619
706
  // depending on whether the new index is before or after the
620
707
  // original index.
621
708
  const decorationPos =
622
709
  cellResolvedPos.pos +
623
- (newIndex > this.view.state.draggingState.originalIndex
624
- ? cellNode.nodeSize - 2
625
- : 0);
710
+ (newIndex > originalIndex ? cellNode.nodeSize - 2 : 0);
626
711
  decorations.push(
627
712
  // The widget is a small bar which spans the width of the cell.
628
713
  Decoration.widget(decorationPos, () => {
@@ -635,9 +720,7 @@ export class TableHandlesProsemirrorPlugin<
635
720
  // table cells is an odd number of pixels. So this makes the
636
721
  // positioning slightly more consistent regardless of where
637
722
  // the row is being dropped.
638
- if (
639
- newIndex > this.view!.state!.draggingState!.originalIndex
640
- ) {
723
+ if (newIndex > originalIndex) {
641
724
  widget.style.bottom = "-2px";
642
725
  } else {
643
726
  widget.style.top = "-3px";
@@ -647,18 +730,22 @@ export class TableHandlesProsemirrorPlugin<
647
730
  return widget;
648
731
  })
649
732
  );
650
- }
733
+ });
651
734
  } else {
652
- // Iterates over all rows in the table.
653
- for (let i = 0; i < tableNode.childCount; i++) {
735
+ const cellsInColumn = getCellsAtColumnHandle(
736
+ this.view.state.block,
737
+ newIndex
738
+ );
739
+
740
+ cellsInColumn.forEach(({ row, col }) => {
654
741
  // Gets each row in the table.
655
742
  const rowResolvedPos = state.doc.resolve(
656
- tableResolvedPos.posAtIndex(i) + 1
743
+ tableResolvedPos.posAtIndex(row) + 1
657
744
  );
658
745
 
659
- // Gets the cell at the new index in the row.
746
+ // Gets the cell within the row.
660
747
  const cellResolvedPos = state.doc.resolve(
661
- rowResolvedPos.posAtIndex(newIndex) + 1
748
+ rowResolvedPos.posAtIndex(col) + 1
662
749
  );
663
750
  const cellNode = cellResolvedPos.node();
664
751
 
@@ -667,9 +754,7 @@ export class TableHandlesProsemirrorPlugin<
667
754
  // original index.
668
755
  const decorationPos =
669
756
  cellResolvedPos.pos +
670
- (newIndex > this.view.state.draggingState.originalIndex
671
- ? cellNode.nodeSize - 2
672
- : 0);
757
+ (newIndex > originalIndex ? cellNode.nodeSize - 2 : 0);
673
758
 
674
759
  decorations.push(
675
760
  // The widget is a small bar which spans the height of the cell.
@@ -683,9 +768,7 @@ export class TableHandlesProsemirrorPlugin<
683
768
  // table cells is an odd number of pixels. So this makes the
684
769
  // positioning slightly more consistent regardless of where
685
770
  // the column is being dropped.
686
- if (
687
- newIndex > this.view!.state!.draggingState!.originalIndex
688
- ) {
771
+ if (newIndex > originalIndex) {
689
772
  widget.style.right = "-2px";
690
773
  } else {
691
774
  widget.style.left = "-3px";
@@ -695,7 +778,7 @@ export class TableHandlesProsemirrorPlugin<
695
778
  return widget;
696
779
  })
697
780
  );
698
- }
781
+ });
699
782
  }
700
783
 
701
784
  return DecorationSet.create(state.doc, decorations);
@@ -834,4 +917,273 @@ export class TableHandlesProsemirrorPlugin<
834
917
  unfreezeHandles = () => {
835
918
  this.view!.menuFrozen = false;
836
919
  };
920
+
921
+ getCellsAtRowHandle = (
922
+ block: BlockFromConfigNoChildren<DefaultBlockSchema["table"], any, any>,
923
+ relativeRowIndex: RelativeCellIndices["row"]
924
+ ) => {
925
+ return getCellsAtRowHandle(block, relativeRowIndex);
926
+ };
927
+
928
+ /**
929
+ * Get all the cells in a column of the table block.
930
+ */
931
+ getCellsAtColumnHandle = (
932
+ block: BlockFromConfigNoChildren<DefaultBlockSchema["table"], any, any>,
933
+ relativeColumnIndex: RelativeCellIndices["col"]
934
+ ) => {
935
+ return getCellsAtColumnHandle(block, relativeColumnIndex);
936
+ };
937
+
938
+ /**
939
+ * Sets the selection to the given cell or a range of cells.
940
+ * @returns The new state after the selection has been set.
941
+ */
942
+ private setCellSelection = (
943
+ relativeStartCell: RelativeCellIndices,
944
+ relativeEndCell: RelativeCellIndices = relativeStartCell
945
+ ) => {
946
+ const view = this.view;
947
+
948
+ if (!view) {
949
+ throw new Error("Table handles view not initialized");
950
+ }
951
+
952
+ const state = this.editor.prosemirrorState;
953
+ const tableResolvedPos = state.doc.resolve(view.tablePos! + 1);
954
+ const startRowResolvedPos = state.doc.resolve(
955
+ tableResolvedPos.posAtIndex(relativeStartCell.row) + 1
956
+ );
957
+ const startCellResolvedPos = state.doc.resolve(
958
+ // No need for +1, since CellSelection expects the position before the cell
959
+ startRowResolvedPos.posAtIndex(relativeStartCell.col)
960
+ );
961
+ const endRowResolvedPos = state.doc.resolve(
962
+ tableResolvedPos.posAtIndex(relativeEndCell.row) + 1
963
+ );
964
+ const endCellResolvedPos = state.doc.resolve(
965
+ // No need for +1, since CellSelection expects the position before the cell
966
+ endRowResolvedPos.posAtIndex(relativeEndCell.col)
967
+ );
968
+
969
+ // Begin a new transaction to set the selection
970
+ const tr = state.tr;
971
+
972
+ // Set the selection to the given cell or a range of cells
973
+ tr.setSelection(
974
+ new CellSelection(startCellResolvedPos, endCellResolvedPos)
975
+ );
976
+
977
+ // Quickly apply the transaction to get the new state to update the selection before splitting the cell
978
+ return state.apply(tr);
979
+ };
980
+
981
+ /**
982
+ * Adds a row or column to the table using prosemirror-table commands
983
+ */
984
+ addRowOrColumn = (
985
+ index: RelativeCellIndices["row"] | RelativeCellIndices["col"],
986
+ direction:
987
+ | { orientation: "row"; side: "above" | "below" }
988
+ | { orientation: "column"; side: "left" | "right" }
989
+ ) => {
990
+ const state = this.setCellSelection(
991
+ direction.orientation === "row"
992
+ ? { row: index, col: 0 }
993
+ : { row: 0, col: index }
994
+ );
995
+ if (direction.orientation === "row") {
996
+ if (direction.side === "above") {
997
+ return addRowBefore(state, this.editor.dispatch);
998
+ } else {
999
+ return addRowAfter(state, this.editor.dispatch);
1000
+ }
1001
+ } else {
1002
+ if (direction.side === "left") {
1003
+ return addColumnBefore(state, this.editor.dispatch);
1004
+ } else {
1005
+ return addColumnAfter(state, this.editor.dispatch);
1006
+ }
1007
+ }
1008
+ };
1009
+
1010
+ /**
1011
+ * Removes a row or column from the table using prosemirror-table commands
1012
+ */
1013
+ removeRowOrColumn = (
1014
+ index: RelativeCellIndices["row"] | RelativeCellIndices["col"],
1015
+ direction: "row" | "column"
1016
+ ) => {
1017
+ const state = this.setCellSelection(
1018
+ direction === "row" ? { row: index, col: 0 } : { row: 0, col: index }
1019
+ );
1020
+
1021
+ if (direction === "row") {
1022
+ return deleteRow(state, this.editor.dispatch);
1023
+ } else {
1024
+ return deleteColumn(state, this.editor.dispatch);
1025
+ }
1026
+ };
1027
+
1028
+ /**
1029
+ * Merges the cells in the table block.
1030
+ */
1031
+ mergeCells = (cellsToMerge?: {
1032
+ relativeStartCell: RelativeCellIndices;
1033
+ relativeEndCell: RelativeCellIndices;
1034
+ }) => {
1035
+ const state = cellsToMerge
1036
+ ? this.setCellSelection(
1037
+ cellsToMerge.relativeStartCell,
1038
+ cellsToMerge.relativeEndCell
1039
+ )
1040
+ : this.editor.prosemirrorState;
1041
+
1042
+ return mergeCells(state, this.editor.dispatch);
1043
+ };
1044
+
1045
+ /**
1046
+ * Splits the cell in the table block.
1047
+ * If no cell is provided, the current cell selected will be split.
1048
+ */
1049
+ splitCell = (relativeCellToSplit?: RelativeCellIndices) => {
1050
+ const state = relativeCellToSplit
1051
+ ? this.setCellSelection(relativeCellToSplit)
1052
+ : this.editor.prosemirrorState;
1053
+
1054
+ return splitCell(state, this.editor.dispatch);
1055
+ };
1056
+
1057
+ /**
1058
+ * Gets the start and end cells of the current cell selection.
1059
+ * @returns The start and end cells of the current cell selection.
1060
+ */
1061
+ getCellSelection = ():
1062
+ | undefined
1063
+ | {
1064
+ from: RelativeCellIndices;
1065
+ to: RelativeCellIndices;
1066
+ /**
1067
+ * All of the cells that are within the selected range.
1068
+ */
1069
+ cells: RelativeCellIndices[];
1070
+ } => {
1071
+ // Based on the current selection, find the table cells that are within the selected range
1072
+ const state = this.editor.prosemirrorState;
1073
+ const selection = state.selection;
1074
+
1075
+ let $fromCell = selection.$from;
1076
+ let $toCell = selection.$to;
1077
+ if (isTableCellSelection(selection)) {
1078
+ // When the selection is a table cell selection, we can find the
1079
+ // from and to cells by iterating over the ranges in the selection
1080
+ const { ranges } = selection;
1081
+ ranges.forEach((range) => {
1082
+ $fromCell = range.$from.min($fromCell ?? range.$from);
1083
+ $toCell = range.$to.max($toCell ?? range.$to);
1084
+ });
1085
+ } else {
1086
+ // When the selection is a normal text selection
1087
+ // Assumes we are within a tableParagraph
1088
+ // And find the from and to cells by resolving the positions
1089
+ $fromCell = state.doc.resolve(
1090
+ selection.$from.pos - selection.$from.parentOffset - 1
1091
+ );
1092
+ $toCell = state.doc.resolve(
1093
+ selection.$to.pos - selection.$to.parentOffset - 1
1094
+ );
1095
+
1096
+ // Opt-out when the selection is not pointing into cells
1097
+ if ($fromCell.pos === 0 || $toCell.pos === 0) {
1098
+ return undefined;
1099
+ }
1100
+ }
1101
+
1102
+ // Find the row and table that the from and to cells are in
1103
+ const $fromRow = state.doc.resolve(
1104
+ $fromCell.pos - $fromCell.parentOffset - 1
1105
+ );
1106
+ const $toRow = state.doc.resolve($toCell.pos - $toCell.parentOffset - 1);
1107
+
1108
+ // Find the table
1109
+ const $table = state.doc.resolve($fromRow.pos - $fromRow.parentOffset - 1);
1110
+
1111
+ // Find the column and row indices of the from and to cells
1112
+ const fromColIndex = $fromCell.index($fromRow.depth);
1113
+ const fromRowIndex = $fromRow.index($table.depth);
1114
+ const toColIndex = $toCell.index($toRow.depth);
1115
+ const toRowIndex = $toRow.index($table.depth);
1116
+
1117
+ const cells: RelativeCellIndices[] = [];
1118
+ for (let row = fromRowIndex; row <= toRowIndex; row++) {
1119
+ for (let col = fromColIndex; col <= toColIndex; col++) {
1120
+ cells.push({ row, col });
1121
+ }
1122
+ }
1123
+
1124
+ return {
1125
+ from: {
1126
+ row: fromRowIndex,
1127
+ col: fromColIndex,
1128
+ },
1129
+ to: {
1130
+ row: toRowIndex,
1131
+ col: toColIndex,
1132
+ },
1133
+ cells,
1134
+ };
1135
+ };
1136
+
1137
+ /**
1138
+ * Gets the direction of the merge based on the current cell selection.
1139
+ *
1140
+ * Returns undefined when there is no cell selection, or the selection is not within a table.
1141
+ */
1142
+ getMergeDirection = (
1143
+ block:
1144
+ | BlockFromConfigNoChildren<DefaultBlockSchema["table"], any, any>
1145
+ | undefined
1146
+ ) => {
1147
+ const isSelectingTableCells = isTableCellSelection(
1148
+ this.editor.prosemirrorState.selection
1149
+ )
1150
+ ? this.editor.prosemirrorState.selection
1151
+ : undefined;
1152
+
1153
+ if (
1154
+ !isSelectingTableCells ||
1155
+ !block ||
1156
+ // Only offer the merge button if there is more than one cell selected.
1157
+ isSelectingTableCells.ranges.length <= 1
1158
+ ) {
1159
+ return undefined;
1160
+ }
1161
+
1162
+ const cellSelection = this.getCellSelection();
1163
+
1164
+ if (!cellSelection) {
1165
+ return undefined;
1166
+ }
1167
+
1168
+ if (areInSameColumn(cellSelection.from, cellSelection.to, block)) {
1169
+ return "vertical";
1170
+ }
1171
+
1172
+ return "horizontal";
1173
+ };
1174
+
1175
+ cropEmptyRowsOrColumns = (
1176
+ block: BlockFromConfigNoChildren<DefaultBlockSchema["table"], any, any>,
1177
+ removeEmpty: "columns" | "rows"
1178
+ ) => {
1179
+ return cropEmptyRowsOrColumns(block, removeEmpty);
1180
+ };
1181
+
1182
+ addRowsOrColumns = (
1183
+ block: BlockFromConfigNoChildren<DefaultBlockSchema["table"], any, any>,
1184
+ addType: "columns" | "rows",
1185
+ numToAdd: number
1186
+ ) => {
1187
+ return addRowsOrColumns(block, addType, numToAdd);
1188
+ };
837
1189
  }
@@ -14,6 +14,8 @@ export const TextAlignmentExtension = Extension.create({
14
14
  "bulletListItem",
15
15
  "numberedListItem",
16
16
  "checkListItem",
17
+ "tableCell",
18
+ "tableHeader",
17
19
  ],
18
20
  attributes: {
19
21
  textAlignment: {
@@ -7,7 +7,7 @@ export const TextColorExtension = Extension.create({
7
7
  addGlobalAttributes() {
8
8
  return [
9
9
  {
10
- types: ["blockContainer"],
10
+ types: ["blockContainer", "tableCell", "tableHeader"],
11
11
  attributes: {
12
12
  textColor: {
13
13
  default: defaultProps.textColor.default,
@@ -252,8 +252,9 @@ const UniqueID = Extension.create({
252
252
  };
253
253
  },
254
254
  props: {
255
- // `handleDOMEvents` is called before `transformPasted`
256
- // so we can do some checks before
255
+ // `handleDOMEvents` is called before `transformPasted` so we can do
256
+ // some checks before. However, `transformPasted` only runs when
257
+ // editor content is pasted - not external content.
257
258
  handleDOMEvents: {
258
259
  // only create new ids for dropped content while holding `alt`
259
260
  // or content is dragged from another editor
@@ -265,9 +266,13 @@ const UniqueID = Extension.create({
265
266
  ? void 0
266
267
  : _a.effectAllowed) === "copy"
267
268
  ) {
268
- dragSourceElement = null;
269
269
  transformPasted = true;
270
+ } else {
271
+ transformPasted = false;
270
272
  }
273
+
274
+ dragSourceElement = null;
275
+
271
276
  return false;
272
277
  },
273
278
  // always create new ids on pasted content