@blocknote/core 0.46.2 → 0.47.1

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 (54) hide show
  1. package/dist/{BlockNoteSchema-DmFDeA0n.cjs → BlockNoteSchema-CwhtPpVC.cjs} +2 -2
  2. package/dist/{BlockNoteSchema-DmFDeA0n.cjs.map → BlockNoteSchema-CwhtPpVC.cjs.map} +1 -1
  3. package/dist/{BlockNoteSchema-BkXw8HJ6.js → BlockNoteSchema-dmbNkHA-.js} +2 -2
  4. package/dist/{BlockNoteSchema-BkXw8HJ6.js.map → BlockNoteSchema-dmbNkHA-.js.map} +1 -1
  5. package/dist/TrailingNode-DHOdUVUO.cjs +2 -0
  6. package/dist/TrailingNode-DHOdUVUO.cjs.map +1 -0
  7. package/dist/{TrailingNode-CxM966vN.js → TrailingNode-F9hX_UlQ.js} +451 -445
  8. package/dist/TrailingNode-F9hX_UlQ.js.map +1 -0
  9. package/dist/blocknote.cjs +4 -4
  10. package/dist/blocknote.cjs.map +1 -1
  11. package/dist/blocknote.js +1624 -1370
  12. package/dist/blocknote.js.map +1 -1
  13. package/dist/blocks.cjs +1 -1
  14. package/dist/blocks.js +2 -2
  15. package/dist/{defaultBlocks-DosClM5E.cjs → defaultBlocks-CSB5GiAu.cjs} +4 -4
  16. package/dist/defaultBlocks-CSB5GiAu.cjs.map +1 -0
  17. package/dist/{defaultBlocks-DE5GNdJH.js → defaultBlocks-Caw1U1oV.js} +49 -46
  18. package/dist/defaultBlocks-Caw1U1oV.js.map +1 -0
  19. package/dist/extensions.cjs +1 -1
  20. package/dist/extensions.js +3 -3
  21. package/dist/locales.cjs +1 -1
  22. package/dist/locales.cjs.map +1 -1
  23. package/dist/locales.js +813 -28
  24. package/dist/locales.js.map +1 -1
  25. package/dist/style.css +1 -1
  26. package/dist/tsconfig.tsbuildinfo +1 -1
  27. package/dist/webpack-stats.json +1 -1
  28. package/package.json +1 -1
  29. package/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts +30 -7
  30. package/src/blocks/ListItem/CheckListItem/block.test.ts +61 -0
  31. package/src/blocks/ListItem/CheckListItem/block.ts +4 -0
  32. package/src/editor/Block.css +2 -2
  33. package/src/editor/transformPasted.ts +69 -0
  34. package/src/extensions/Collaboration/YCursorPlugin.ts +3 -1
  35. package/src/extensions/SideMenu/SideMenu.ts +44 -0
  36. package/src/extensions/SuggestionMenu/SuggestionMenu.test.ts +191 -0
  37. package/src/extensions/SuggestionMenu/SuggestionMenu.ts +28 -11
  38. package/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +470 -64
  39. package/src/i18n/locales/fa.ts +390 -0
  40. package/src/i18n/locales/index.ts +2 -0
  41. package/src/i18n/locales/uz.ts +421 -0
  42. package/src/schema/blocks/createSpec.ts +2 -0
  43. package/types/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.d.ts +5 -0
  44. package/types/src/blocks/ListItem/CheckListItem/block.test.d.ts +1 -0
  45. package/types/src/extensions/SuggestionMenu/SuggestionMenu.d.ts +12 -3
  46. package/types/src/extensions/SuggestionMenu/SuggestionMenu.test.d.ts +1 -0
  47. package/types/src/i18n/locales/fa.d.ts +320 -0
  48. package/types/src/i18n/locales/index.d.ts +2 -0
  49. package/types/src/i18n/locales/uz.d.ts +2 -0
  50. package/dist/TrailingNode-CxM966vN.js.map +0 -1
  51. package/dist/TrailingNode-D-CZ76FS.cjs +0 -2
  52. package/dist/TrailingNode-D-CZ76FS.cjs.map +0 -1
  53. package/dist/defaultBlocks-DE5GNdJH.js.map +0 -1
  54. package/dist/defaultBlocks-DosClM5E.cjs.map +0 -1
@@ -1,8 +1,11 @@
1
1
  import { Extension } from "@tiptap/core";
2
-
2
+ import { Fragment, Node } from "prosemirror-model";
3
3
  import { TextSelection } from "prosemirror-state";
4
+
4
5
  import {
5
6
  getBottomNestedBlockInfo,
7
+ getNextBlockInfo,
8
+ getParentBlockInfo,
6
9
  getPrevBlockInfo,
7
10
  mergeBlocksCommand,
8
11
  } from "../../../api/blockManipulation/commands/mergeBlocks/mergeBlocks.js";
@@ -10,7 +13,10 @@ import { nestBlock } from "../../../api/blockManipulation/commands/nestBlock/nes
10
13
  import { fixColumnList } from "../../../api/blockManipulation/commands/replaceBlocks/util/fixColumnList.js";
11
14
  import { splitBlockCommand } from "../../../api/blockManipulation/commands/splitBlock/splitBlock.js";
12
15
  import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock/updateBlock.js";
13
- import { getBlockInfoFromSelection } from "../../../api/getBlockInfoFromPos.js";
16
+ import {
17
+ getBlockInfoFromResolvedPos,
18
+ getBlockInfoFromSelection,
19
+ } from "../../../api/getBlockInfoFromPos.js";
14
20
  import { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js";
15
21
  import { FormattingToolbarExtension } from "../../FormattingToolbar/FormattingToolbar.js";
16
22
  import { FilePanelExtension } from "../../FilePanel/FilePanel.js";
@@ -83,6 +89,21 @@ export const KeyboardShortcutsExtension = Extension.create<{
83
89
  }
84
90
  const { bnBlock: blockContainer, blockContent } = blockInfo;
85
91
 
92
+ const prevBlockInfo = getPrevBlockInfo(
93
+ state.doc,
94
+ blockInfo.bnBlock.beforePos,
95
+ );
96
+ // If the previous block has no inline content, it can't be merged.
97
+ // It's instead deleted, which is done later in the chan, so we
98
+ // return early here.
99
+ if (
100
+ !prevBlockInfo ||
101
+ !prevBlockInfo.isBlockContainer ||
102
+ prevBlockInfo.blockContent.node.type.spec.content !== "inline*"
103
+ ) {
104
+ return false;
105
+ }
106
+
86
107
  const selectionAtBlockStart =
87
108
  state.selection.from === blockContent.beforePos + 1;
88
109
  const selectionEmpty = state.selection.empty;
@@ -98,9 +119,46 @@ export const KeyboardShortcutsExtension = Extension.create<{
98
119
 
99
120
  return false;
100
121
  }),
122
+ // If the previous block is a columnList, moves the current block to
123
+ // the end of the last column in it.
124
+ () =>
125
+ commands.command(({ state, tr, dispatch }) => {
126
+ const blockInfo = getBlockInfoFromSelection(state);
127
+ if (!blockInfo.isBlockContainer) {
128
+ return false;
129
+ }
130
+
131
+ const prevBlockInfo = getPrevBlockInfo(
132
+ state.doc,
133
+ blockInfo.bnBlock.beforePos,
134
+ );
135
+ if (!prevBlockInfo || prevBlockInfo.isBlockContainer) {
136
+ return false;
137
+ }
138
+
139
+ if (dispatch) {
140
+ const columnAfterPos = prevBlockInfo.bnBlock.afterPos - 1;
141
+ const $blockAfterPos = tr.doc.resolve(columnAfterPos - 1);
142
+
143
+ tr.delete(
144
+ blockInfo.bnBlock.beforePos,
145
+ blockInfo.bnBlock.afterPos,
146
+ );
147
+ tr.insert($blockAfterPos.pos, blockInfo.bnBlock.node);
148
+ tr.setSelection(
149
+ TextSelection.near(tr.doc.resolve($blockAfterPos.pos + 1)),
150
+ );
151
+
152
+ return true;
153
+ }
154
+
155
+ return false;
156
+ }),
157
+ // If the block is the first in a column, moves it to the end of the
158
+ // previous column. If there is no previous column, moves it above the
159
+ // columnList.
101
160
  () =>
102
161
  commands.command(({ state, tr, dispatch }) => {
103
- // when at the start of a first block in a column
104
162
  const blockInfo = getBlockInfoFromSelection(state);
105
163
  if (!blockInfo.isBlockContainer) {
106
164
  return false;
@@ -116,7 +174,6 @@ export const KeyboardShortcutsExtension = Extension.create<{
116
174
 
117
175
  const prevBlock = $pos.nodeBefore;
118
176
  if (prevBlock) {
119
- // should be no previous block
120
177
  return false;
121
178
  }
122
179
 
@@ -130,31 +187,22 @@ export const KeyboardShortcutsExtension = Extension.create<{
130
187
  const columnListPos = $columnPos.before();
131
188
 
132
189
  if (dispatch) {
133
- const fragment = tr.doc.slice(
134
- blockInfo.bnBlock.beforePos,
135
- blockInfo.bnBlock.afterPos,
136
- ).content;
137
-
138
190
  tr.delete(
139
191
  blockInfo.bnBlock.beforePos,
140
192
  blockInfo.bnBlock.afterPos,
141
193
  );
194
+ fixColumnList(tr, columnListPos);
142
195
 
143
- if ($columnPos.index() === 0) {
144
- // Fix `columnList` and insert the block before it.
145
- fixColumnList(tr, columnListPos);
146
- tr.insert(columnListPos, fragment);
196
+ if ($columnPos.pos === columnListPos + 1) {
197
+ tr.insert(columnListPos, blockInfo.bnBlock.node);
147
198
  tr.setSelection(
148
199
  TextSelection.near(tr.doc.resolve(columnListPos)),
149
200
  );
150
201
  } else {
151
- // Insert the block at the end of the first column and fix
152
- // `columnList`.
153
- tr.insert($columnPos.pos - 1, fragment);
202
+ tr.insert($columnPos.pos - 1, blockInfo.bnBlock.node);
154
203
  tr.setSelection(
155
- TextSelection.near(tr.doc.resolve($columnPos.pos - 1)),
204
+ TextSelection.near(tr.doc.resolve($columnPos.pos)),
156
205
  );
157
- fixColumnList(tr, columnListPos);
158
206
  }
159
207
  }
160
208
 
@@ -184,6 +232,14 @@ export const KeyboardShortcutsExtension = Extension.create<{
184
232
 
185
233
  let chainedCommands = chain();
186
234
 
235
+ // Moves the children of the current block to the previous one.
236
+ if (blockInfo.childContainer) {
237
+ chainedCommands.insertContentAt(
238
+ blockInfo.bnBlock.afterPos,
239
+ blockInfo.childContainer?.node.content,
240
+ );
241
+ }
242
+
187
243
  if (
188
244
  prevBlockInfo.blockContent.node.type.spec.content ===
189
245
  "tableRow+"
@@ -200,17 +256,12 @@ export const KeyboardShortcutsExtension = Extension.create<{
200
256
  } else if (
201
257
  prevBlockInfo.blockContent.node.type.spec.content === ""
202
258
  ) {
203
- const nonEditableBlockContentStartPos =
204
- prevBlockInfo.blockContent.afterPos -
205
- prevBlockInfo.blockContent.node.nodeSize;
206
-
207
259
  chainedCommands = chainedCommands.setNodeSelection(
208
- nonEditableBlockContentStartPos,
260
+ prevBlockInfo.blockContent.beforePos,
209
261
  );
210
262
  } else {
211
263
  const blockContentStartPos =
212
- prevBlockInfo.blockContent.afterPos -
213
- prevBlockInfo.blockContent.node.nodeSize;
264
+ prevBlockInfo.blockContent.afterPos - 1;
214
265
 
215
266
  chainedCommands =
216
267
  chainedCommands.setTextSelection(blockContentStartPos);
@@ -235,8 +286,7 @@ export const KeyboardShortcutsExtension = Extension.create<{
235
286
  const blockInfo = getBlockInfoFromSelection(state);
236
287
 
237
288
  if (!blockInfo.isBlockContainer) {
238
- // TODO
239
- throw new Error(`todo`);
289
+ return false;
240
290
  }
241
291
 
242
292
  const selectionAtBlockStart =
@@ -255,8 +305,7 @@ export const KeyboardShortcutsExtension = Extension.create<{
255
305
  );
256
306
 
257
307
  if (!bottomBlock.isBlockContainer) {
258
- // TODO
259
- throw new Error(`todo`);
308
+ return false;
260
309
  }
261
310
 
262
311
  const prevBlockNotTableAndNoContent =
@@ -287,50 +336,388 @@ export const KeyboardShortcutsExtension = Extension.create<{
287
336
  ]);
288
337
 
289
338
  const handleDelete = () =>
290
- this.editor.commands.first(({ commands }) => [
339
+ this.editor.commands.first(({ chain, commands }) => [
291
340
  // Deletes the selection if it's not empty.
292
341
  () => commands.deleteSelection(),
342
+ // Deletes the first child block and un-nests its children, if the
343
+ // selection is empty and at the end of the current block. If both the
344
+ // parent and child blocks have inline content, the child block's
345
+ // content is appended to the parent's. The child block's own children
346
+ // are unindented before it's deleted.
347
+ () =>
348
+ commands.command(({ state }) => {
349
+ const blockInfo = getBlockInfoFromSelection(state);
350
+ if (!blockInfo.isBlockContainer || !blockInfo.childContainer) {
351
+ return false;
352
+ }
353
+ const { blockContent, childContainer } = blockInfo;
354
+
355
+ const selectionAtBlockEnd =
356
+ state.selection.from === blockContent.afterPos - 1;
357
+ const selectionEmpty = state.selection.empty;
358
+
359
+ const firstChildBlockInfo = getBlockInfoFromResolvedPos(
360
+ state.doc.resolve(childContainer.beforePos + 1),
361
+ );
362
+ if (!firstChildBlockInfo.isBlockContainer) {
363
+ return false;
364
+ }
365
+
366
+ if (selectionAtBlockEnd && selectionEmpty) {
367
+ const firstChildBlockContent =
368
+ firstChildBlockInfo.blockContent.node;
369
+ const firstChildBlockHasInlineContent =
370
+ firstChildBlockContent.type.spec.content === "inline*";
371
+ const blockHasInlineContent =
372
+ blockContent.node.type.spec.content === "inline*";
373
+
374
+ return (
375
+ chain()
376
+ // Un-nests child block's children if necessary.
377
+ .insertContentAt(
378
+ firstChildBlockInfo.bnBlock.afterPos,
379
+ firstChildBlockInfo.childContainer?.node.content ||
380
+ Fragment.empty,
381
+ )
382
+ .deleteRange(
383
+ // Deletes whole child container if there's only one child.
384
+ childContainer.node.childCount === 1
385
+ ? {
386
+ from: childContainer.beforePos,
387
+ to: childContainer.afterPos,
388
+ }
389
+ : {
390
+ from: firstChildBlockInfo.bnBlock.beforePos,
391
+ to: firstChildBlockInfo.bnBlock.afterPos,
392
+ },
393
+ )
394
+ // Appends inline content from child block if possible.
395
+ .insertContentAt(
396
+ state.selection.from,
397
+ firstChildBlockHasInlineContent && blockHasInlineContent
398
+ ? firstChildBlockContent.content
399
+ : null,
400
+ )
401
+ .setTextSelection(state.selection.from)
402
+ .scrollIntoView()
403
+ .run()
404
+ );
405
+ }
406
+
407
+ return false;
408
+ }),
293
409
  // Merges block with the next one (at the same nesting level or lower),
294
410
  // if one exists, the block has no children, and the selection is at the
295
411
  // end of the block.
296
412
  () =>
297
413
  commands.command(({ state }) => {
298
- // TODO: Change this to not rely on offsets & schema assumptions
299
414
  const blockInfo = getBlockInfoFromSelection(state);
300
415
  if (!blockInfo.isBlockContainer) {
301
416
  return false;
302
417
  }
303
- const {
304
- bnBlock: blockContainer,
305
- blockContent,
306
- childContainer,
307
- } = blockInfo;
418
+ const { bnBlock: blockContainer, blockContent } = blockInfo;
419
+
420
+ const nextBlockInfo = getNextBlockInfo(
421
+ state.doc,
422
+ blockInfo.bnBlock.beforePos,
423
+ );
424
+ if (!nextBlockInfo || !nextBlockInfo.isBlockContainer) {
425
+ return false;
426
+ }
308
427
 
309
- const { depth } = state.doc.resolve(blockContainer.beforePos);
310
- const blockAtDocEnd =
311
- blockContainer.afterPos === state.doc.nodeSize - 3;
312
428
  const selectionAtBlockEnd =
313
429
  state.selection.from === blockContent.afterPos - 1;
314
430
  const selectionEmpty = state.selection.empty;
315
- const hasChildBlocks = childContainer !== undefined;
316
431
 
317
- if (
318
- !blockAtDocEnd &&
319
- selectionAtBlockEnd &&
320
- selectionEmpty &&
321
- !hasChildBlocks
322
- ) {
323
- let oldDepth = depth;
324
- let newPos = blockContainer.afterPos + 1;
325
- let newDepth = state.doc.resolve(newPos).depth;
326
-
327
- while (newDepth < oldDepth) {
328
- oldDepth = newDepth;
329
- newPos += 2;
330
- newDepth = state.doc.resolve(newPos).depth;
432
+ const posBetweenBlocks = blockContainer.afterPos;
433
+
434
+ if (selectionAtBlockEnd && selectionEmpty) {
435
+ return chain()
436
+ .command(mergeBlocksCommand(posBetweenBlocks))
437
+ .scrollIntoView()
438
+ .run();
439
+ }
440
+
441
+ return false;
442
+ }),
443
+ // If the previous block is a columnList, moves the current block to
444
+ // the end of the last column in it.
445
+ () =>
446
+ commands.command(({ state, tr, dispatch }) => {
447
+ const blockInfo = getBlockInfoFromSelection(state);
448
+ if (!blockInfo.isBlockContainer) {
449
+ return false;
450
+ }
451
+
452
+ const nextBlockInfo = getNextBlockInfo(
453
+ state.doc,
454
+ blockInfo.bnBlock.beforePos,
455
+ );
456
+ if (!nextBlockInfo || nextBlockInfo.isBlockContainer) {
457
+ return false;
458
+ }
459
+
460
+ if (dispatch) {
461
+ const columnBeforePos = nextBlockInfo.bnBlock.beforePos + 1;
462
+ const $blockBeforePos = tr.doc.resolve(columnBeforePos + 1);
463
+
464
+ tr.delete(
465
+ $blockBeforePos.pos,
466
+ $blockBeforePos.pos + $blockBeforePos.nodeAfter!.nodeSize,
467
+ );
468
+ fixColumnList(tr, nextBlockInfo.bnBlock.beforePos);
469
+ tr.insert(blockInfo.bnBlock.afterPos, $blockBeforePos.nodeAfter!);
470
+ tr.setSelection(
471
+ TextSelection.near(tr.doc.resolve($blockBeforePos.pos)),
472
+ );
473
+
474
+ return true;
475
+ }
476
+
477
+ return false;
478
+ }),
479
+ // If the block is the last in a column, moves it to the start of the
480
+ // next column. If there is no next column, moves it below the
481
+ // columnList.
482
+ () =>
483
+ commands.command(({ state, tr, dispatch }) => {
484
+ const blockInfo = getBlockInfoFromSelection(state);
485
+ if (!blockInfo.isBlockContainer) {
486
+ return false;
487
+ }
488
+
489
+ const selectionAtBlockEnd =
490
+ tr.selection.from === blockInfo.blockContent.afterPos - 1;
491
+ if (!selectionAtBlockEnd) {
492
+ return false;
493
+ }
494
+
495
+ const $pos = tr.doc.resolve(blockInfo.bnBlock.afterPos);
496
+
497
+ const nextBlock = $pos.nodeAfter;
498
+ if (nextBlock) {
499
+ return false;
500
+ }
501
+
502
+ const parentBlock = $pos.node();
503
+ if (parentBlock.type.name !== "column") {
504
+ return false;
505
+ }
506
+
507
+ const $blockEndPos = tr.doc.resolve(blockInfo.bnBlock.afterPos);
508
+ const $columnEndPos = tr.doc.resolve($blockEndPos.after());
509
+ const columnListEndPos = $columnEndPos.after();
510
+
511
+ if (dispatch) {
512
+ // Position before first block in next column, or first block
513
+ // after columnList if there is no next column.
514
+ const nextBlockBeforePos =
515
+ $columnEndPos.pos === columnListEndPos - 1
516
+ ? columnListEndPos
517
+ : $columnEndPos.pos + 1;
518
+ const nextBlockInfo = getBlockInfoFromResolvedPos(
519
+ tr.doc.resolve(nextBlockBeforePos),
520
+ );
521
+
522
+ tr.delete(
523
+ nextBlockInfo.bnBlock.beforePos,
524
+ nextBlockInfo.bnBlock.afterPos,
525
+ );
526
+ fixColumnList(
527
+ tr,
528
+ columnListEndPos - $columnEndPos.node().nodeSize,
529
+ );
530
+ tr.insert($blockEndPos.pos, nextBlockInfo.bnBlock.node);
531
+ tr.setSelection(
532
+ TextSelection.near(tr.doc.resolve(nextBlockBeforePos)),
533
+ );
534
+ }
535
+
536
+ return true;
537
+ }),
538
+ // Deletes the next block at either the same or lower nesting level, if
539
+ // the selection is empty and at the end of the block. If both the
540
+ // current and next blocks have inline content, the next block's
541
+ // content is appended to the current block's. The next block's own
542
+ // children are unindented before it's deleted.
543
+ () =>
544
+ commands.command(({ state }) => {
545
+ const blockInfo = getBlockInfoFromSelection(state);
546
+ if (!blockInfo.isBlockContainer) {
547
+ return false;
548
+ }
549
+ const { blockContent } = blockInfo;
550
+
551
+ const selectionAtBlockEnd =
552
+ state.selection.from === blockContent.afterPos - 1;
553
+ const selectionEmpty = state.selection.empty;
554
+
555
+ if (selectionAtBlockEnd && selectionEmpty) {
556
+ const getNextBlockInfoAtAnyLevel = (
557
+ doc: Node,
558
+ beforePos: number,
559
+ ) => {
560
+ const nextBlockInfo = getNextBlockInfo(doc, beforePos);
561
+ if (nextBlockInfo) {
562
+ return nextBlockInfo;
563
+ }
564
+
565
+ const parentBlockInfo = getParentBlockInfo(doc, beforePos);
566
+ if (!parentBlockInfo) {
567
+ return undefined;
568
+ }
569
+
570
+ return getNextBlockInfoAtAnyLevel(
571
+ doc,
572
+ parentBlockInfo.bnBlock.beforePos,
573
+ );
574
+ };
575
+
576
+ const nextBlockInfo = getNextBlockInfoAtAnyLevel(
577
+ state.doc,
578
+ blockInfo.bnBlock.beforePos,
579
+ );
580
+ if (!nextBlockInfo || !nextBlockInfo.isBlockContainer) {
581
+ return false;
331
582
  }
332
583
 
333
- return commands.command(mergeBlocksCommand(newPos - 1));
584
+ const nextBlockContent = nextBlockInfo.blockContent.node;
585
+ const nextBlockHasInlineContent =
586
+ nextBlockContent.type.spec.content === "inline*";
587
+ const blockHasInlineContent =
588
+ blockContent.node.type.spec.content === "inline*";
589
+
590
+ return (
591
+ chain()
592
+ // Un-nests next block's children if necessary.
593
+ .insertContentAt(
594
+ nextBlockInfo.bnBlock.afterPos,
595
+ nextBlockInfo.childContainer?.node.content ||
596
+ Fragment.empty,
597
+ )
598
+ .deleteRange({
599
+ from: nextBlockInfo.bnBlock.beforePos,
600
+ to: nextBlockInfo.bnBlock.afterPos,
601
+ })
602
+ // Appends inline content from child block if possible.
603
+ .insertContentAt(
604
+ state.selection.from,
605
+ nextBlockHasInlineContent && blockHasInlineContent
606
+ ? nextBlockContent.content
607
+ : null,
608
+ )
609
+ .setTextSelection(state.selection.from)
610
+ .scrollIntoView()
611
+ .run()
612
+ );
613
+ }
614
+
615
+ return false;
616
+ }),
617
+ // Deletes the current block if it's an empty block with inline content,
618
+ // and moves the selection to the next block.
619
+ () =>
620
+ commands.command(({ state }) => {
621
+ const blockInfo = getBlockInfoFromSelection(state);
622
+ if (!blockInfo.isBlockContainer) {
623
+ return false;
624
+ }
625
+
626
+ const blockEmpty =
627
+ blockInfo.blockContent.node.childCount === 0 &&
628
+ blockInfo.blockContent.node.type.spec.content === "inline*";
629
+
630
+ if (blockEmpty) {
631
+ const nextBlockInfo = getNextBlockInfo(
632
+ state.doc,
633
+ blockInfo.bnBlock.beforePos,
634
+ );
635
+ if (!nextBlockInfo || !nextBlockInfo.isBlockContainer) {
636
+ return false;
637
+ }
638
+
639
+ let chainedCommands = chain();
640
+
641
+ if (
642
+ nextBlockInfo.blockContent.node.type.spec.content ===
643
+ "tableRow+"
644
+ ) {
645
+ const tableBlockStartPos = blockInfo.bnBlock.afterPos + 1;
646
+ const tableBlockContentStartPos = tableBlockStartPos + 1;
647
+ const firstRowStartPos = tableBlockContentStartPos + 1;
648
+ const firstCellStartPos = firstRowStartPos + 1;
649
+ const firstCellParagraphStartPos = firstCellStartPos + 1;
650
+
651
+ chainedCommands = chainedCommands.setTextSelection(
652
+ firstCellParagraphStartPos,
653
+ );
654
+ } else if (
655
+ nextBlockInfo.blockContent.node.type.spec.content === ""
656
+ ) {
657
+ chainedCommands = chainedCommands.setNodeSelection(
658
+ nextBlockInfo.blockContent.beforePos,
659
+ );
660
+ } else {
661
+ chainedCommands = chainedCommands.setTextSelection(
662
+ nextBlockInfo.blockContent.beforePos + 1,
663
+ );
664
+ }
665
+
666
+ return chainedCommands
667
+ .deleteRange({
668
+ from: blockInfo.bnBlock.beforePos,
669
+ to: blockInfo.bnBlock.afterPos,
670
+ })
671
+ .scrollIntoView()
672
+ .run();
673
+ }
674
+
675
+ return false;
676
+ }),
677
+ // Deletes next block if it contains no content and isn't a table,
678
+ // when the selection is empty and at the end of the block. Moves the
679
+ // current block into the deleted block's place.
680
+ () =>
681
+ commands.command(({ state }) => {
682
+ const blockInfo = getBlockInfoFromSelection(state);
683
+
684
+ if (!blockInfo.isBlockContainer) {
685
+ return false;
686
+ }
687
+
688
+ const selectionAtBlockEnd =
689
+ state.selection.from === blockInfo.blockContent.afterPos - 1;
690
+ const selectionEmpty = state.selection.empty;
691
+
692
+ const nextBlockInfo = getNextBlockInfo(
693
+ state.doc,
694
+ blockInfo.bnBlock.beforePos,
695
+ );
696
+ if (!nextBlockInfo) {
697
+ return false;
698
+ }
699
+ if (!nextBlockInfo.isBlockContainer) {
700
+ return false;
701
+ }
702
+
703
+ if (nextBlockInfo && selectionAtBlockEnd && selectionEmpty) {
704
+ const nextBlockNotTableAndNoContent =
705
+ nextBlockInfo.blockContent.node.type.spec.content === "" ||
706
+ (nextBlockInfo.blockContent.node.type.spec.content ===
707
+ "inline*" &&
708
+ nextBlockInfo.blockContent.node.childCount === 0);
709
+
710
+ if (nextBlockNotTableAndNoContent) {
711
+ const childBlocks =
712
+ nextBlockInfo.bnBlock.node.lastChild!.content;
713
+ return chain()
714
+ .deleteRange({
715
+ from: nextBlockInfo.bnBlock.beforePos,
716
+ to: nextBlockInfo.bnBlock.afterPos,
717
+ })
718
+ .insertContentAt(blockInfo.bnBlock.afterPos, nextBlockInfo.bnBlock.node.childCount === 2 ? childBlocks : null)
719
+ .run();
720
+ }
334
721
  }
335
722
 
336
723
  return false;
@@ -413,7 +800,7 @@ export const KeyboardShortcutsExtension = Extension.create<{
413
800
  // Creates a new block and moves the selection to it if the current one is empty, while the selection is also
414
801
  // empty & at the start of the block.
415
802
  () =>
416
- commands.command(({ state, dispatch }) => {
803
+ commands.command(({ state, dispatch, tr }) => {
417
804
  const blockInfo = getBlockInfoFromSelection(state);
418
805
  if (!blockInfo.isBlockContainer) {
419
806
  return false;
@@ -431,15 +818,34 @@ export const KeyboardShortcutsExtension = Extension.create<{
431
818
  const newBlockContentPos = newBlockInsertionPos + 2;
432
819
 
433
820
  if (dispatch) {
434
- const newBlock =
435
- state.schema.nodes["blockContainer"].createAndFill()!;
436
-
437
- state.tr
438
- .insert(newBlockInsertionPos, newBlock)
821
+ // Creates a new block with the children of the current block,
822
+ // if it has any.
823
+ const newBlock = state.schema.nodes[
824
+ "blockContainer"
825
+ ].createAndFill(
826
+ undefined,
827
+ [
828
+ state.schema.nodes["paragraph"].createAndFill() ||
829
+ undefined,
830
+ blockInfo.childContainer?.node,
831
+ ].filter((node) => node !== undefined),
832
+ )!;
833
+
834
+ // Inserts the new block and moves the selection to it.
835
+ tr.insert(newBlockInsertionPos, newBlock)
836
+ .setSelection(
837
+ new TextSelection(tr.doc.resolve(newBlockContentPos)),
838
+ )
439
839
  .scrollIntoView();
440
- state.tr.setSelection(
441
- new TextSelection(state.doc.resolve(newBlockContentPos)),
442
- );
840
+
841
+ // Deletes old block's children, as they have been moved to
842
+ // the new one.
843
+ if (blockInfo.childContainer) {
844
+ tr.delete(
845
+ blockInfo.childContainer.beforePos,
846
+ blockInfo.childContainer.afterPos,
847
+ );
848
+ }
443
849
  }
444
850
 
445
851
  return true;