@blocknote/core 0.32.0 → 0.34.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 (43) hide show
  1. package/dist/blocknote.cjs +8 -8
  2. package/dist/blocknote.cjs.map +1 -1
  3. package/dist/blocknote.js +1816 -1665
  4. package/dist/blocknote.js.map +1 -1
  5. package/dist/tsconfig.tsbuildinfo +1 -1
  6. package/dist/webpack-stats.json +1 -1
  7. package/package.json +2 -2
  8. package/src/api/__snapshots__/blocks-indented-changed.json +129 -0
  9. package/src/api/__snapshots__/blocks-moved-deeper-into-nesting.json +164 -0
  10. package/src/api/__snapshots__/blocks-moved-multiple-in-same-transaction.json +188 -0
  11. package/src/api/__snapshots__/blocks-moved-to-different-parent.json +78 -0
  12. package/src/api/__snapshots__/blocks-moved-to-root-level.json +78 -0
  13. package/src/api/__snapshots__/blocks-outdented-changed.json +129 -0
  14. package/src/api/blockManipulation/commands/nestBlock/nestBlock.ts +58 -59
  15. package/src/api/nodeUtil.test.ts +228 -1
  16. package/src/api/nodeUtil.ts +135 -118
  17. package/src/api/parsers/markdown/detectMarkdown.test.ts +211 -0
  18. package/src/api/parsers/markdown/detectMarkdown.ts +3 -2
  19. package/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +2 -1
  20. package/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +2 -1
  21. package/src/blocks/ToggleWrapper/createToggleWrapper.ts +2 -0
  22. package/src/blocks/defaultBlockTypeGuards.ts +30 -0
  23. package/src/editor/BlockNoteEditor.ts +27 -10
  24. package/src/editor/BlockNoteExtensions.ts +3 -5
  25. package/src/exporter/Exporter.ts +2 -0
  26. package/src/exporter/mapping.ts +1 -0
  27. package/src/extensions/BlockChange/BlockChangePlugin.ts +66 -0
  28. package/src/extensions/Collaboration/CursorPlugin.ts +33 -2
  29. package/src/extensions/SideMenu/SideMenuPlugin.ts +290 -207
  30. package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +22 -12
  31. package/src/schema/inlineContent/types.ts +8 -0
  32. package/src/util/browser.ts +11 -1
  33. package/types/src/api/nodeUtil.d.ts +15 -21
  34. package/types/src/api/parsers/markdown/detectMarkdown.test.d.ts +1 -0
  35. package/types/src/blocks/defaultBlockTypeGuards.d.ts +7 -1
  36. package/types/src/editor/BlockNoteEditor.d.ts +11 -8
  37. package/types/src/editor/BlockNoteExtensions.d.ts +0 -1
  38. package/types/src/exporter/Exporter.d.ts +1 -1
  39. package/types/src/exporter/mapping.d.ts +1 -1
  40. package/types/src/extensions/BlockChange/BlockChangePlugin.d.ts +15 -0
  41. package/types/src/extensions/Collaboration/CursorPlugin.d.ts +6 -0
  42. package/types/src/extensions/SideMenu/SideMenuPlugin.d.ts +50 -9
  43. package/types/src/schema/inlineContent/types.d.ts +4 -0
@@ -0,0 +1,78 @@
1
+ [
2
+ {
3
+ "block": {
4
+ "children": [],
5
+ "content": [
6
+ {
7
+ "styles": {},
8
+ "text": "Child 1",
9
+ "type": "text",
10
+ },
11
+ ],
12
+ "id": "child-1",
13
+ "props": {
14
+ "backgroundColor": "default",
15
+ "textAlignment": "left",
16
+ "textColor": "default",
17
+ },
18
+ "type": "paragraph",
19
+ },
20
+ "currentParent": undefined,
21
+ "prevBlock": {
22
+ "children": [],
23
+ "content": [
24
+ {
25
+ "styles": {},
26
+ "text": "Child 1",
27
+ "type": "text",
28
+ },
29
+ ],
30
+ "id": "child-1",
31
+ "props": {
32
+ "backgroundColor": "default",
33
+ "textAlignment": "left",
34
+ "textColor": "default",
35
+ },
36
+ "type": "paragraph",
37
+ },
38
+ "prevParent": {
39
+ "children": [
40
+ {
41
+ "children": [],
42
+ "content": [
43
+ {
44
+ "styles": {},
45
+ "text": "Child 1",
46
+ "type": "text",
47
+ },
48
+ ],
49
+ "id": "child-1",
50
+ "props": {
51
+ "backgroundColor": "default",
52
+ "textAlignment": "left",
53
+ "textColor": "default",
54
+ },
55
+ "type": "paragraph",
56
+ },
57
+ ],
58
+ "content": [
59
+ {
60
+ "styles": {},
61
+ "text": "Parent 1",
62
+ "type": "text",
63
+ },
64
+ ],
65
+ "id": "parent-1",
66
+ "props": {
67
+ "backgroundColor": "default",
68
+ "textAlignment": "left",
69
+ "textColor": "default",
70
+ },
71
+ "type": "paragraph",
72
+ },
73
+ "source": {
74
+ "type": "local",
75
+ },
76
+ "type": "move",
77
+ },
78
+ ]
@@ -0,0 +1,78 @@
1
+ [
2
+ {
3
+ "block": {
4
+ "children": [],
5
+ "content": [
6
+ {
7
+ "styles": {},
8
+ "text": "Child",
9
+ "type": "text",
10
+ },
11
+ ],
12
+ "id": "child",
13
+ "props": {
14
+ "backgroundColor": "default",
15
+ "textAlignment": "left",
16
+ "textColor": "default",
17
+ },
18
+ "type": "paragraph",
19
+ },
20
+ "currentParent": undefined,
21
+ "prevBlock": {
22
+ "children": [],
23
+ "content": [
24
+ {
25
+ "styles": {},
26
+ "text": "Child",
27
+ "type": "text",
28
+ },
29
+ ],
30
+ "id": "child",
31
+ "props": {
32
+ "backgroundColor": "default",
33
+ "textAlignment": "left",
34
+ "textColor": "default",
35
+ },
36
+ "type": "paragraph",
37
+ },
38
+ "prevParent": {
39
+ "children": [
40
+ {
41
+ "children": [],
42
+ "content": [
43
+ {
44
+ "styles": {},
45
+ "text": "Child",
46
+ "type": "text",
47
+ },
48
+ ],
49
+ "id": "child",
50
+ "props": {
51
+ "backgroundColor": "default",
52
+ "textAlignment": "left",
53
+ "textColor": "default",
54
+ },
55
+ "type": "paragraph",
56
+ },
57
+ ],
58
+ "content": [
59
+ {
60
+ "styles": {},
61
+ "text": "Parent",
62
+ "type": "text",
63
+ },
64
+ ],
65
+ "id": "parent",
66
+ "props": {
67
+ "backgroundColor": "default",
68
+ "textAlignment": "left",
69
+ "textColor": "default",
70
+ },
71
+ "type": "paragraph",
72
+ },
73
+ "source": {
74
+ "type": "local",
75
+ },
76
+ "type": "move",
77
+ },
78
+ ]
@@ -0,0 +1,129 @@
1
+ [
2
+ {
3
+ "block": {
4
+ "children": [],
5
+ "content": [
6
+ {
7
+ "styles": {},
8
+ "text": "C",
9
+ "type": "text",
10
+ },
11
+ ],
12
+ "id": "double-nested-paragraph-0",
13
+ "props": {
14
+ "backgroundColor": "default",
15
+ "textAlignment": "left",
16
+ "textColor": "default",
17
+ },
18
+ "type": "paragraph",
19
+ },
20
+ "currentParent": {
21
+ "children": [
22
+ {
23
+ "children": [],
24
+ "content": [
25
+ {
26
+ "styles": {},
27
+ "text": "B",
28
+ "type": "text",
29
+ },
30
+ ],
31
+ "id": "nested-paragraph-0",
32
+ "props": {
33
+ "backgroundColor": "default",
34
+ "textAlignment": "left",
35
+ "textColor": "default",
36
+ },
37
+ "type": "paragraph",
38
+ },
39
+ {
40
+ "children": [],
41
+ "content": [
42
+ {
43
+ "styles": {},
44
+ "text": "C",
45
+ "type": "text",
46
+ },
47
+ ],
48
+ "id": "double-nested-paragraph-0",
49
+ "props": {
50
+ "backgroundColor": "default",
51
+ "textAlignment": "left",
52
+ "textColor": "default",
53
+ },
54
+ "type": "paragraph",
55
+ },
56
+ ],
57
+ "content": [
58
+ {
59
+ "styles": {},
60
+ "text": "A",
61
+ "type": "text",
62
+ },
63
+ ],
64
+ "id": "paragraph-with-children",
65
+ "props": {
66
+ "backgroundColor": "default",
67
+ "textAlignment": "left",
68
+ "textColor": "default",
69
+ },
70
+ "type": "paragraph",
71
+ },
72
+ "prevBlock": {
73
+ "children": [],
74
+ "content": [
75
+ {
76
+ "styles": {},
77
+ "text": "C",
78
+ "type": "text",
79
+ },
80
+ ],
81
+ "id": "double-nested-paragraph-0",
82
+ "props": {
83
+ "backgroundColor": "default",
84
+ "textAlignment": "left",
85
+ "textColor": "default",
86
+ },
87
+ "type": "paragraph",
88
+ },
89
+ "prevParent": {
90
+ "children": [
91
+ {
92
+ "children": [],
93
+ "content": [
94
+ {
95
+ "styles": {},
96
+ "text": "C",
97
+ "type": "text",
98
+ },
99
+ ],
100
+ "id": "double-nested-paragraph-0",
101
+ "props": {
102
+ "backgroundColor": "default",
103
+ "textAlignment": "left",
104
+ "textColor": "default",
105
+ },
106
+ "type": "paragraph",
107
+ },
108
+ ],
109
+ "content": [
110
+ {
111
+ "styles": {},
112
+ "text": "B",
113
+ "type": "text",
114
+ },
115
+ ],
116
+ "id": "nested-paragraph-0",
117
+ "props": {
118
+ "backgroundColor": "default",
119
+ "textAlignment": "left",
120
+ "textColor": "default",
121
+ },
122
+ "type": "paragraph",
123
+ },
124
+ "source": {
125
+ "type": "local",
126
+ },
127
+ "type": "move",
128
+ },
129
+ ]
@@ -1,5 +1,5 @@
1
1
  import { Fragment, NodeType, Slice } from "prosemirror-model";
2
- import { EditorState, Transaction } from "prosemirror-state";
2
+ import { Transaction } from "prosemirror-state";
3
3
  import { ReplaceAroundStep } from "prosemirror-transform";
4
4
 
5
5
  import { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js";
@@ -11,68 +11,67 @@ import { getBlockInfoFromTransaction } from "../../../getBlockInfoFromPos.js";
11
11
  *
12
12
  * The original function derives too many information from the parentnode and itemtype
13
13
  */
14
- function sinkListItem(itemType: NodeType, groupType: NodeType) {
15
- return function (state: EditorState, dispatch?: (tr: Transaction) => void) {
16
- const { $from, $to } = state.selection;
17
- const range = $from.blockRange(
18
- $to,
19
- (node) =>
20
- node.childCount > 0 &&
21
- (node.type.name === "blockGroup" || node.type.name === "column"), // change necessary to not look at first item child type
22
- );
23
- if (!range) {
24
- return false;
25
- }
26
- const startIndex = range.startIndex;
27
- if (startIndex === 0) {
28
- return false;
29
- }
30
- const parent = range.parent;
31
- const nodeBefore = parent.child(startIndex - 1);
32
- if (nodeBefore.type !== itemType) {
33
- return false;
34
- }
35
- if (dispatch) {
36
- const nestedBefore =
37
- nodeBefore.lastChild && nodeBefore.lastChild.type === groupType; // change necessary to check groupType instead of parent.type
38
- const inner = Fragment.from(nestedBefore ? itemType.create() : null);
39
- const slice = new Slice(
40
- Fragment.from(
41
- itemType.create(null, Fragment.from(groupType.create(null, inner))), // change necessary to create "groupType" instead of parent.type
42
- ),
43
- nestedBefore ? 3 : 1,
44
- 0,
45
- );
14
+ function sinkListItem(
15
+ tr: Transaction,
16
+ itemType: NodeType,
17
+ groupType: NodeType,
18
+ ) {
19
+ const { $from, $to } = tr.selection;
20
+ const range = $from.blockRange(
21
+ $to,
22
+ (node) =>
23
+ node.childCount > 0 &&
24
+ (node.type.name === "blockGroup" || node.type.name === "column"), // change necessary to not look at first item child type
25
+ );
26
+ if (!range) {
27
+ return false;
28
+ }
29
+ const startIndex = range.startIndex;
30
+ if (startIndex === 0) {
31
+ return false;
32
+ }
33
+ const parent = range.parent;
34
+ const nodeBefore = parent.child(startIndex - 1);
35
+ if (nodeBefore.type !== itemType) {
36
+ return false;
37
+ }
38
+ const nestedBefore =
39
+ nodeBefore.lastChild && nodeBefore.lastChild.type === groupType; // change necessary to check groupType instead of parent.type
40
+ const inner = Fragment.from(nestedBefore ? itemType.create() : null);
41
+ const slice = new Slice(
42
+ Fragment.from(
43
+ itemType.create(null, Fragment.from(groupType.create(null, inner))), // change necessary to create "groupType" instead of parent.type
44
+ ),
45
+ nestedBefore ? 3 : 1,
46
+ 0,
47
+ );
48
+
49
+ const before = range.start;
50
+ const after = range.end;
51
+
52
+ tr.step(
53
+ new ReplaceAroundStep(
54
+ before - (nestedBefore ? 3 : 1),
55
+ after,
56
+ before,
57
+ after,
58
+ slice,
59
+ 1,
60
+ true,
61
+ ),
62
+ ).scrollIntoView();
46
63
 
47
- const before = range.start;
48
- const after = range.end;
49
- dispatch(
50
- state.tr
51
- .step(
52
- new ReplaceAroundStep(
53
- before - (nestedBefore ? 3 : 1),
54
- after,
55
- before,
56
- after,
57
- slice,
58
- 1,
59
- true,
60
- ),
61
- )
62
- .scrollIntoView(),
63
- );
64
- }
65
- return true;
66
- };
64
+ return true;
67
65
  }
68
66
 
69
67
  export function nestBlock(editor: BlockNoteEditor<any, any, any>) {
70
- return editor.exec((state, dispatch) =>
71
- sinkListItem(
72
- state.schema.nodes["blockContainer"],
73
- state.schema.nodes["blockGroup"],
74
- )(state, dispatch),
75
- );
68
+ return editor.transact((tr) => {
69
+ return sinkListItem(
70
+ tr,
71
+ editor.pmSchema.nodes["blockContainer"],
72
+ editor.pmSchema.nodes["blockGroup"],
73
+ );
74
+ });
76
75
  }
77
76
 
78
77
  export function unnestBlock(editor: BlockNoteEditor<any, any, any>) {
@@ -6,7 +6,7 @@ import { BlockNoteEditor } from "../editor/BlockNoteEditor.js";
6
6
 
7
7
  const getEditor = setupTestEnv();
8
8
 
9
- describe("Test getBlocksChangedByTransaction", () => {
9
+ describe("getBlocksChangedByTransaction", () => {
10
10
  let editor: BlockNoteEditor;
11
11
 
12
12
  beforeEach(() => {
@@ -225,4 +225,231 @@ describe("Test getBlocksChangedByTransaction", () => {
225
225
  "__snapshots__/blocks-updated-content-inserted.json",
226
226
  );
227
227
  });
228
+
229
+ it("should return blocks which have been indented", async () => {
230
+ editor.replaceBlocks(editor.document, [
231
+ {
232
+ id: "paragraph-with-children",
233
+ type: "paragraph",
234
+ content: "A",
235
+ children: [
236
+ {
237
+ id: "nested-paragraph-0",
238
+ type: "paragraph",
239
+ content: "B",
240
+ children: [],
241
+ },
242
+ {
243
+ id: "double-nested-paragraph-0",
244
+ type: "paragraph",
245
+ content: "C",
246
+ },
247
+ ],
248
+ },
249
+ ]);
250
+ const blocksChanged = editor.transact((tr) => {
251
+ editor.setTextCursorPosition("double-nested-paragraph-0", "start");
252
+ editor.nestBlock();
253
+
254
+ return getBlocksChangedByTransaction(tr);
255
+ });
256
+
257
+ await expect(blocksChanged).toMatchFileSnapshot(
258
+ "__snapshots__/blocks-indented-changed.json",
259
+ );
260
+ });
261
+
262
+ it("should return blocks which have been outdented", async () => {
263
+ editor.replaceBlocks(editor.document, [
264
+ {
265
+ id: "paragraph-with-children",
266
+ type: "paragraph",
267
+ content: "A",
268
+ children: [
269
+ {
270
+ id: "nested-paragraph-0",
271
+ type: "paragraph",
272
+ content: "B",
273
+ children: [
274
+ {
275
+ id: "double-nested-paragraph-0",
276
+ type: "paragraph",
277
+ content: "C",
278
+ },
279
+ ],
280
+ },
281
+ ],
282
+ },
283
+ ]);
284
+
285
+ // This test is different from the other tests because it uses the onChange hook to get the blocks changed
286
+ // This is because unnesting a block is not allowed within a transaction
287
+ let blocksChanged: any = null;
288
+ const unsubscribe = editor.onChange((_e, { getChanges }) => {
289
+ blocksChanged = getChanges();
290
+ });
291
+
292
+ // Make the change
293
+ editor.setTextCursorPosition("double-nested-paragraph-0", "start");
294
+ editor.unnestBlock();
295
+
296
+ // Clean up
297
+ if (unsubscribe) {
298
+ unsubscribe();
299
+ }
300
+
301
+ await expect(blocksChanged).toMatchFileSnapshot(
302
+ "__snapshots__/blocks-outdented-changed.json",
303
+ );
304
+ });
305
+
306
+ it("should return blocks which have been moved to a different parent", async () => {
307
+ editor.replaceBlocks(editor.document, [
308
+ {
309
+ id: "parent-1",
310
+ type: "paragraph",
311
+ content: "Parent 1",
312
+ children: [
313
+ {
314
+ id: "child-1",
315
+ type: "paragraph",
316
+ content: "Child 1",
317
+ },
318
+ ],
319
+ },
320
+ {
321
+ id: "parent-2",
322
+ type: "paragraph",
323
+ content: "Parent 2",
324
+ children: [],
325
+ },
326
+ ]);
327
+
328
+ const blocksChanged = editor.transact((tr) => {
329
+ const childBlock = editor.getBlock("child-1");
330
+ editor.removeBlocks(["child-1"]);
331
+ editor.insertBlocks([{ ...childBlock }], "parent-2", "after");
332
+
333
+ return getBlocksChangedByTransaction(tr);
334
+ });
335
+
336
+ await expect(blocksChanged).toMatchFileSnapshot(
337
+ "__snapshots__/blocks-moved-to-different-parent.json",
338
+ );
339
+ });
340
+
341
+ it("should return blocks which have been moved to root level", async () => {
342
+ editor.replaceBlocks(editor.document, [
343
+ {
344
+ id: "parent",
345
+ type: "paragraph",
346
+ content: "Parent",
347
+ children: [
348
+ {
349
+ id: "child",
350
+ type: "paragraph",
351
+ content: "Child",
352
+ },
353
+ ],
354
+ },
355
+ ]);
356
+
357
+ const blocksChanged = editor.transact((tr) => {
358
+ const childBlock = editor.getBlock("child");
359
+ editor.removeBlocks(["child"]);
360
+ editor.insertBlocks([{ ...childBlock }], "parent", "after");
361
+
362
+ return getBlocksChangedByTransaction(tr);
363
+ });
364
+
365
+ await expect(blocksChanged).toMatchFileSnapshot(
366
+ "__snapshots__/blocks-moved-to-root-level.json",
367
+ );
368
+ });
369
+
370
+ it("should return blocks which have been moved deeper into nesting", async () => {
371
+ editor.replaceBlocks(editor.document, [
372
+ {
373
+ id: "root",
374
+ type: "paragraph",
375
+ content: "Root",
376
+ children: [
377
+ {
378
+ id: "level-1",
379
+ type: "paragraph",
380
+ content: "Level 1",
381
+ children: [
382
+ {
383
+ id: "level-2",
384
+ type: "paragraph",
385
+ content: "Level 2",
386
+ },
387
+ ],
388
+ },
389
+ {
390
+ id: "target",
391
+ type: "paragraph",
392
+ content: "Target",
393
+ },
394
+ ],
395
+ },
396
+ ]);
397
+
398
+ const blocksChanged = editor.transact((tr) => {
399
+ const targetBlock = editor.getBlock("target");
400
+ editor.removeBlocks(["target"]);
401
+ editor.insertBlocks([{ ...targetBlock }], "level-2", "after");
402
+
403
+ return getBlocksChangedByTransaction(tr);
404
+ });
405
+
406
+ await expect(blocksChanged).toMatchFileSnapshot(
407
+ "__snapshots__/blocks-moved-deeper-into-nesting.json",
408
+ );
409
+ });
410
+
411
+ it("should return multiple blocks when multiple blocks are moved in the same transaction", async () => {
412
+ editor.replaceBlocks(editor.document, [
413
+ {
414
+ id: "parent-1",
415
+ type: "paragraph",
416
+ content: "Parent 1",
417
+ children: [
418
+ {
419
+ id: "child-1",
420
+ type: "paragraph",
421
+ content: "Child 1",
422
+ },
423
+ {
424
+ id: "child-2",
425
+ type: "paragraph",
426
+ content: "Child 2",
427
+ },
428
+ ],
429
+ },
430
+ {
431
+ id: "parent-2",
432
+ type: "paragraph",
433
+ content: "Parent 2",
434
+ children: [],
435
+ },
436
+ ]);
437
+
438
+ const blocksChanged = editor.transact((tr) => {
439
+ const child1Block = editor.getBlock("child-1");
440
+ const child2Block = editor.getBlock("child-2");
441
+ editor.removeBlocks(["child-1", "child-2"]);
442
+ editor.insertBlocks(
443
+ [{ ...child1Block }, { ...child2Block }],
444
+ "parent-2",
445
+ "after",
446
+ );
447
+
448
+ return getBlocksChangedByTransaction(tr);
449
+ });
450
+
451
+ await expect(blocksChanged).toMatchFileSnapshot(
452
+ "__snapshots__/blocks-moved-multiple-in-same-transaction.json",
453
+ );
454
+ });
228
455
  });