@blocknote/core 0.47.0 → 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.
- package/dist/{BlockNoteSchema-DT4bdXj5.cjs → BlockNoteSchema-CwhtPpVC.cjs} +2 -2
- package/dist/{BlockNoteSchema-DT4bdXj5.cjs.map → BlockNoteSchema-CwhtPpVC.cjs.map} +1 -1
- package/dist/{BlockNoteSchema-1r-ln0Q0.js → BlockNoteSchema-dmbNkHA-.js} +2 -2
- package/dist/{BlockNoteSchema-1r-ln0Q0.js.map → BlockNoteSchema-dmbNkHA-.js.map} +1 -1
- package/dist/TrailingNode-DHOdUVUO.cjs +2 -0
- package/dist/TrailingNode-DHOdUVUO.cjs.map +1 -0
- package/dist/{TrailingNode-DZag-Nvu.js → TrailingNode-F9hX_UlQ.js} +5 -3
- package/dist/TrailingNode-F9hX_UlQ.js.map +1 -0
- package/dist/blocknote.cjs +4 -4
- package/dist/blocknote.cjs.map +1 -1
- package/dist/blocknote.js +1169 -954
- package/dist/blocknote.js.map +1 -1
- package/dist/blocks.cjs +1 -1
- package/dist/blocks.js +2 -2
- package/dist/{defaultBlocks-D049Pbme.cjs → defaultBlocks-CSB5GiAu.cjs} +5 -5
- package/dist/defaultBlocks-CSB5GiAu.cjs.map +1 -0
- package/dist/{defaultBlocks-BSOEW3GR.js → defaultBlocks-Caw1U1oV.js} +47 -44
- package/dist/defaultBlocks-Caw1U1oV.js.map +1 -0
- package/dist/extensions.cjs +1 -1
- package/dist/extensions.js +3 -3
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/webpack-stats.json +1 -1
- package/package.json +1 -1
- package/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts +30 -7
- package/src/extensions/Collaboration/YCursorPlugin.ts +3 -1
- package/src/extensions/SuggestionMenu/SuggestionMenu.test.ts +191 -0
- package/src/extensions/SuggestionMenu/SuggestionMenu.ts +28 -11
- package/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +433 -53
- package/src/schema/blocks/createSpec.ts +2 -0
- package/types/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.d.ts +5 -0
- package/types/src/extensions/SuggestionMenu/SuggestionMenu.d.ts +12 -3
- package/types/src/extensions/SuggestionMenu/SuggestionMenu.test.d.ts +1 -0
- package/dist/TrailingNode-DZag-Nvu.js.map +0 -1
- package/dist/TrailingNode-tesI8f7N.cjs +0 -2
- package/dist/TrailingNode-tesI8f7N.cjs.map +0 -1
- package/dist/defaultBlocks-BSOEW3GR.js.map +0 -1
- package/dist/defaultBlocks-D049Pbme.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 {
|
|
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.
|
|
144
|
-
|
|
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
|
-
|
|
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
|
|
204
|
+
TextSelection.near(tr.doc.resolve($columnPos.pos)),
|
|
156
205
|
);
|
|
157
|
-
fixColumnList(tr, columnListPos);
|
|
158
206
|
}
|
|
159
207
|
}
|
|
160
208
|
|
|
@@ -208,12 +256,8 @@ export const KeyboardShortcutsExtension = Extension.create<{
|
|
|
208
256
|
} else if (
|
|
209
257
|
prevBlockInfo.blockContent.node.type.spec.content === ""
|
|
210
258
|
) {
|
|
211
|
-
const nonEditableBlockContentStartPos =
|
|
212
|
-
prevBlockInfo.blockContent.afterPos -
|
|
213
|
-
prevBlockInfo.blockContent.node.nodeSize;
|
|
214
|
-
|
|
215
259
|
chainedCommands = chainedCommands.setNodeSelection(
|
|
216
|
-
|
|
260
|
+
prevBlockInfo.blockContent.beforePos,
|
|
217
261
|
);
|
|
218
262
|
} else {
|
|
219
263
|
const blockContentStartPos =
|
|
@@ -242,8 +286,7 @@ export const KeyboardShortcutsExtension = Extension.create<{
|
|
|
242
286
|
const blockInfo = getBlockInfoFromSelection(state);
|
|
243
287
|
|
|
244
288
|
if (!blockInfo.isBlockContainer) {
|
|
245
|
-
|
|
246
|
-
throw new Error(`todo`);
|
|
289
|
+
return false;
|
|
247
290
|
}
|
|
248
291
|
|
|
249
292
|
const selectionAtBlockStart =
|
|
@@ -262,8 +305,7 @@ export const KeyboardShortcutsExtension = Extension.create<{
|
|
|
262
305
|
);
|
|
263
306
|
|
|
264
307
|
if (!bottomBlock.isBlockContainer) {
|
|
265
|
-
|
|
266
|
-
throw new Error(`todo`);
|
|
308
|
+
return false;
|
|
267
309
|
}
|
|
268
310
|
|
|
269
311
|
const prevBlockNotTableAndNoContent =
|
|
@@ -294,50 +336,388 @@ export const KeyboardShortcutsExtension = Extension.create<{
|
|
|
294
336
|
]);
|
|
295
337
|
|
|
296
338
|
const handleDelete = () =>
|
|
297
|
-
this.editor.commands.first(({ commands }) => [
|
|
339
|
+
this.editor.commands.first(({ chain, commands }) => [
|
|
298
340
|
// Deletes the selection if it's not empty.
|
|
299
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
|
+
}),
|
|
300
409
|
// Merges block with the next one (at the same nesting level or lower),
|
|
301
410
|
// if one exists, the block has no children, and the selection is at the
|
|
302
411
|
// end of the block.
|
|
303
412
|
() =>
|
|
304
413
|
commands.command(({ state }) => {
|
|
305
|
-
// TODO: Change this to not rely on offsets & schema assumptions
|
|
306
414
|
const blockInfo = getBlockInfoFromSelection(state);
|
|
307
415
|
if (!blockInfo.isBlockContainer) {
|
|
308
416
|
return false;
|
|
309
417
|
}
|
|
310
|
-
const {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
+
}
|
|
315
427
|
|
|
316
|
-
const { depth } = state.doc.resolve(blockContainer.beforePos);
|
|
317
|
-
const blockAtDocEnd =
|
|
318
|
-
blockContainer.afterPos === state.doc.nodeSize - 3;
|
|
319
428
|
const selectionAtBlockEnd =
|
|
320
429
|
state.selection.from === blockContent.afterPos - 1;
|
|
321
430
|
const selectionEmpty = state.selection.empty;
|
|
322
|
-
const hasChildBlocks = childContainer !== undefined;
|
|
323
431
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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;
|
|
338
582
|
}
|
|
339
583
|
|
|
340
|
-
|
|
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
|
+
}
|
|
341
721
|
}
|
|
342
722
|
|
|
343
723
|
return false;
|
|
@@ -210,6 +210,8 @@ export function addNodeAndExtensionsToSpec<
|
|
|
210
210
|
|
|
211
211
|
// See explanation for why `update` is not implemented for NodeViews
|
|
212
212
|
// https://github.com/TypeCellOS/BlockNote/pull/1904#discussion_r2313461464
|
|
213
|
+
// TODO: in a future version, we might want to implement updates so that
|
|
214
|
+
// vanilla blocks don't always re-render entirely (https://github.com/TypeCellOS/BlockNote/issues/220)
|
|
213
215
|
return nodeView;
|
|
214
216
|
};
|
|
215
217
|
},
|
|
@@ -11,6 +11,11 @@ export declare const getParentBlockInfo: (doc: Node, beforePos: number) => Block
|
|
|
11
11
|
* or undefined if the given block is the first sibling.
|
|
12
12
|
*/
|
|
13
13
|
export declare const getPrevBlockInfo: (doc: Node, beforePos: number) => BlockInfo | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Returns the block info from the sibling block after (below) the given block,
|
|
16
|
+
* or undefined if the given block is the last sibling.
|
|
17
|
+
*/
|
|
18
|
+
export declare const getNextBlockInfo: (doc: Node, beforePos: number) => BlockInfo | undefined;
|
|
14
19
|
/**
|
|
15
20
|
* If a block has children like this:
|
|
16
21
|
* A
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Plugin } from "prosemirror-state";
|
|
1
|
+
import { Plugin, Transaction } from "prosemirror-state";
|
|
2
2
|
import { UiElementPosition } from "../../extensions-shared/UiElementPosition.js";
|
|
3
3
|
export type SuggestionMenuState = UiElementPosition & {
|
|
4
4
|
query: string;
|
|
@@ -12,6 +12,15 @@ type SuggestionPluginState = {
|
|
|
12
12
|
decorationId: string;
|
|
13
13
|
ignoreQueryLength?: boolean;
|
|
14
14
|
} | undefined;
|
|
15
|
+
export type SuggestionMenuOptions = {
|
|
16
|
+
triggerCharacter: string;
|
|
17
|
+
/**
|
|
18
|
+
* Optional callback to determine whether the suggestion menu should be
|
|
19
|
+
* opened in the current editor state. Return `false` to prevent the
|
|
20
|
+
* menu from opening (e.g. when the cursor is inside table content).
|
|
21
|
+
*/
|
|
22
|
+
shouldOpen?: (tr: Transaction) => boolean;
|
|
23
|
+
};
|
|
15
24
|
/**
|
|
16
25
|
* A ProseMirror plugin for suggestions, designed to make '/'-commands possible as well as mentions.
|
|
17
26
|
*
|
|
@@ -40,8 +49,8 @@ export declare const SuggestionMenu: (options?: any) => import("../../index.js")
|
|
|
40
49
|
} & {
|
|
41
50
|
triggerCharacter: string;
|
|
42
51
|
}) | undefined>;
|
|
43
|
-
readonly
|
|
44
|
-
readonly
|
|
52
|
+
readonly addSuggestionMenu: (options: SuggestionMenuOptions) => void;
|
|
53
|
+
readonly removeSuggestionMenu: (triggerCharacter: string) => void;
|
|
45
54
|
readonly closeMenu: () => void;
|
|
46
55
|
readonly clearQuery: () => void;
|
|
47
56
|
readonly shown: () => boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|