@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.
- package/dist/{BlockNoteSchema-DmFDeA0n.cjs → BlockNoteSchema-CwhtPpVC.cjs} +2 -2
- package/dist/{BlockNoteSchema-DmFDeA0n.cjs.map → BlockNoteSchema-CwhtPpVC.cjs.map} +1 -1
- package/dist/{BlockNoteSchema-BkXw8HJ6.js → BlockNoteSchema-dmbNkHA-.js} +2 -2
- package/dist/{BlockNoteSchema-BkXw8HJ6.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-CxM966vN.js → TrailingNode-F9hX_UlQ.js} +451 -445
- 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 +1624 -1370
- package/dist/blocknote.js.map +1 -1
- package/dist/blocks.cjs +1 -1
- package/dist/blocks.js +2 -2
- package/dist/{defaultBlocks-DosClM5E.cjs → defaultBlocks-CSB5GiAu.cjs} +4 -4
- package/dist/defaultBlocks-CSB5GiAu.cjs.map +1 -0
- package/dist/{defaultBlocks-DE5GNdJH.js → defaultBlocks-Caw1U1oV.js} +49 -46
- package/dist/defaultBlocks-Caw1U1oV.js.map +1 -0
- package/dist/extensions.cjs +1 -1
- package/dist/extensions.js +3 -3
- package/dist/locales.cjs +1 -1
- package/dist/locales.cjs.map +1 -1
- package/dist/locales.js +813 -28
- package/dist/locales.js.map +1 -1
- package/dist/style.css +1 -1
- 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/blocks/ListItem/CheckListItem/block.test.ts +61 -0
- package/src/blocks/ListItem/CheckListItem/block.ts +4 -0
- package/src/editor/Block.css +2 -2
- package/src/editor/transformPasted.ts +69 -0
- package/src/extensions/Collaboration/YCursorPlugin.ts +3 -1
- package/src/extensions/SideMenu/SideMenu.ts +44 -0
- 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 +470 -64
- package/src/i18n/locales/fa.ts +390 -0
- package/src/i18n/locales/index.ts +2 -0
- package/src/i18n/locales/uz.ts +421 -0
- package/src/schema/blocks/createSpec.ts +2 -0
- package/types/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.d.ts +5 -0
- package/types/src/blocks/ListItem/CheckListItem/block.test.d.ts +1 -0
- package/types/src/extensions/SuggestionMenu/SuggestionMenu.d.ts +12 -3
- package/types/src/extensions/SuggestionMenu/SuggestionMenu.test.d.ts +1 -0
- package/types/src/i18n/locales/fa.d.ts +320 -0
- package/types/src/i18n/locales/index.d.ts +2 -0
- package/types/src/i18n/locales/uz.d.ts +2 -0
- package/dist/TrailingNode-CxM966vN.js.map +0 -1
- package/dist/TrailingNode-D-CZ76FS.cjs +0 -2
- package/dist/TrailingNode-D-CZ76FS.cjs.map +0 -1
- package/dist/defaultBlocks-DE5GNdJH.js.map +0 -1
- 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 {
|
|
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
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
|
|
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
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
-
|
|
441
|
-
|
|
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;
|