@blocknote/core 0.2.2 → 0.2.4-alpha.7

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 (89) hide show
  1. package/dist/blocknote.js +1061 -936
  2. package/dist/blocknote.js.map +1 -1
  3. package/dist/blocknote.umd.cjs +1 -1
  4. package/dist/blocknote.umd.cjs.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +22 -29
  7. package/src/BlockNoteExtensions.ts +11 -10
  8. package/src/extensions/BackgroundColor/BackgroundColorExtension.ts +61 -0
  9. package/src/extensions/BackgroundColor/BackgroundColorMark.ts +62 -0
  10. package/src/extensions/Blocks/PreviousBlockTypePlugin.ts +112 -106
  11. package/src/extensions/Blocks/apiTypes.ts +48 -0
  12. package/src/extensions/Blocks/helpers/findBlock.ts +3 -1
  13. package/src/extensions/Blocks/helpers/getBlockInfoFromPos.ts +1 -1
  14. package/src/extensions/Blocks/index.ts +10 -8
  15. package/src/extensions/Blocks/nodes/Block.module.css +122 -35
  16. package/src/extensions/Blocks/{BlockAttributes.ts → nodes/BlockAttributes.ts} +0 -0
  17. package/src/extensions/Blocks/nodes/{Block.ts → BlockContainer.ts} +113 -119
  18. package/src/extensions/Blocks/nodes/{BlockTypes/HeadingBlock/HeadingContent.ts → BlockContent/HeadingBlockContent/HeadingBlockContent.ts} +16 -24
  19. package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +76 -0
  20. package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/ListItemKeyboardShortcuts.ts +47 -0
  21. package/src/extensions/Blocks/nodes/{BlockTypes/ListItemBlock/OrderedListItemIndexPlugin.ts → BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts} +10 -14
  22. package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +95 -0
  23. package/src/extensions/Blocks/nodes/{BlockTypes/TextBlock/TextContent.ts → BlockContent/ParagraphBlockContent/ParagraphBlockContent.ts} +7 -12
  24. package/src/extensions/Blocks/nodes/BlockGroup.ts +4 -4
  25. package/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.ts +9 -1
  26. package/src/extensions/DraggableBlocks/DraggableBlocksPlugin.ts +87 -42
  27. package/src/extensions/{Blocks → DraggableBlocks}/MultipleNodeSelection.ts +0 -0
  28. package/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.ts +20 -7
  29. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +51 -12
  30. package/src/extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes.ts +1 -1
  31. package/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.ts +3 -1
  32. package/src/extensions/Placeholder/PlaceholderExtension.ts +1 -1
  33. package/src/extensions/SlashMenu/SlashMenuExtension.ts +1 -1
  34. package/src/extensions/SlashMenu/SlashMenuItem.ts +3 -28
  35. package/src/extensions/SlashMenu/defaultCommands.tsx +36 -55
  36. package/src/extensions/SlashMenu/index.ts +1 -6
  37. package/src/extensions/TextAlignment/TextAlignmentExtension.ts +75 -0
  38. package/src/extensions/TextColor/TextColorExtension.ts +54 -0
  39. package/src/extensions/TextColor/TextColorMark.ts +62 -0
  40. package/src/extensions/TrailingNode/TrailingNodeExtension.ts +4 -4
  41. package/src/extensions/UniqueID/UniqueID.ts +6 -0
  42. package/src/index.ts +2 -1
  43. package/src/shared/EditorElement.ts +12 -6
  44. package/src/shared/plugins/suggestion/SuggestionItem.ts +0 -9
  45. package/src/shared/plugins/suggestion/SuggestionPlugin.ts +191 -228
  46. package/src/shared/plugins/suggestion/SuggestionsMenuFactoryTypes.ts +2 -2
  47. package/types/src/BlockNoteEditor.d.ts +1 -1
  48. package/types/src/BlockNoteExtensions.d.ts +1 -3
  49. package/types/src/api/Document.d.ts +5 -0
  50. package/types/src/extensions/BackgroundColor/BackgroundColorExtension.d.ts +9 -0
  51. package/types/src/extensions/BackgroundColor/BackgroundColorMark.d.ts +9 -0
  52. package/types/src/extensions/Blocks/PreviousBlockTypePlugin.d.ts +3 -2
  53. package/types/src/extensions/Blocks/apiTypes.d.ts +16 -0
  54. package/types/src/extensions/Blocks/helpers/getBlockInfoFromPos.d.ts +1 -1
  55. package/types/src/extensions/Blocks/nodes/BlockAttributes.d.ts +2 -0
  56. package/types/src/extensions/Blocks/nodes/BlockContainer.d.ts +21 -0
  57. package/types/src/extensions/Blocks/nodes/BlockContent/BlockContentTypes.d.ts +4 -0
  58. package/types/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.d.ts +2 -0
  59. package/types/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContentTypes.d.ts +4 -0
  60. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.d.ts +2 -0
  61. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContentTypes.d.ts +2 -0
  62. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/ListItemKeyboardShortcuts.d.ts +2 -0
  63. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.d.ts +2 -0
  64. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.d.ts +2 -0
  65. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContentTypes.d.ts +2 -0
  66. package/types/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent.d.ts +2 -0
  67. package/types/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContentTypes.d.ts +2 -0
  68. package/types/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.d.ts +9 -5
  69. package/types/src/extensions/DraggableBlocks/DraggableBlocksExtension.d.ts +1 -1
  70. package/types/src/extensions/DraggableBlocks/DraggableBlocksPlugin.d.ts +6 -11
  71. package/types/src/extensions/DraggableBlocks/MultipleNodeSelection.d.ts +24 -0
  72. package/types/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.d.ts +18 -8
  73. package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +1 -1
  74. package/types/src/extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes.d.ts +5 -5
  75. package/types/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.d.ts +2 -2
  76. package/types/src/extensions/SlashMenu/SlashMenuExtension.d.ts +1 -1
  77. package/types/src/extensions/SlashMenu/SlashMenuItem.d.ts +2 -19
  78. package/types/src/extensions/SlashMenu/defaultSlashCommands.d.ts +5 -0
  79. package/types/src/extensions/SlashMenu/index.d.ts +1 -2
  80. package/types/src/extensions/TextAlignment/TextAlignmentExtension.d.ts +9 -0
  81. package/types/src/extensions/TextColor/TextColorExtension.d.ts +9 -0
  82. package/types/src/extensions/TextColor/TextColorMark.d.ts +9 -0
  83. package/types/src/index.d.ts +2 -1
  84. package/types/src/shared/EditorElement.d.ts +6 -2
  85. package/types/src/shared/plugins/suggestion/SuggestionItem.d.ts +0 -6
  86. package/types/src/shared/plugins/suggestion/SuggestionPlugin.d.ts +11 -25
  87. package/types/src/shared/plugins/suggestion/SuggestionsMenuFactoryTypes.d.ts +6 -6
  88. package/src/extensions/Blocks/nodes/BlockTypes/ListItemBlock/ListItemContent.ts +0 -177
  89. package/src/extensions/Paragraph/FixedParagraph.ts +0 -12
@@ -10,12 +10,6 @@ BASIC STYLES
10
10
  font-weight: normal;
11
11
  }
12
12
 
13
- .block {
14
- border-left: 2px solid white;
15
- border-color: white;
16
- transition: all 0.2s;
17
- }
18
-
19
13
  .block {
20
14
  /* content: ""; */
21
15
  transition: all 0.2s;
@@ -115,34 +109,34 @@ NESTED BLOCKS
115
109
  }
116
110
 
117
111
  /* HEADINGS*/
118
- [data-heading-level="1"] {
112
+ [data-level="1"] {
119
113
  --level: 3em;
120
114
  }
121
- [data-heading-level="2"] {
115
+ [data-level="2"] {
122
116
  --level: 2em;
123
117
  }
124
- [data-heading-level="3"] {
118
+ [data-level="3"] {
125
119
  --level: 1.3em;
126
120
  }
127
121
 
128
- [data-prev-heading-level="1"] {
122
+ [data-prev-level="1"] {
129
123
  --prev-level: 3em;
130
124
  }
131
- [data-prev-heading-level="2"] {
125
+ [data-prev-level="2"] {
132
126
  --prev-level: 2em;
133
127
  }
134
- [data-prev-heading-level="3"] {
128
+ [data-prev-level="3"] {
135
129
  --prev-level: 1.3em;
136
130
  }
137
131
 
138
- .blockOuter[data-prev-type="headingContent"] > .block > .blockContent {
132
+ .blockOuter[data-prev-type="heading"] > .block > .blockContent {
139
133
  font-size: var(--prev-level);
140
134
  font-weight: bold;
141
135
  }
142
136
 
143
137
  .blockOuter:not([data-prev-type])
144
138
  > .block
145
- > .blockContent[data-content-type="headingContent"] {
139
+ > .blockContent[data-content-type="heading"] {
146
140
  font-size: var(--level);
147
141
  font-weight: bold;
148
142
  }
@@ -155,15 +149,15 @@ NESTED BLOCKS
155
149
  }
156
150
 
157
151
  /* Ordered */
158
- [data-list-item-type="ordered"] {
159
- --index: attr(data-list-item-index)
152
+ [data-content-type="numberedListItem"] {
153
+ --index: attr(data-index)
160
154
  }
161
155
 
162
- [data-prev-list-item-type="ordered"] {
163
- --prev-index: attr(data-prev-list-item-index)
156
+ [data-prev-type="numberedListItem"] {
157
+ --prev-index: attr(data-prev-index)
164
158
  }
165
159
 
166
- .blockOuter[data-prev-list-item-type="ordered"]:not([data-prev-list-item-index="none"])
160
+ .blockOuter[data-prev-type="numberedListItem"]:not([data-prev-index="none"])
167
161
  > .block
168
162
  > .blockContent::before {
169
163
  margin-right: 1.2em;
@@ -172,14 +166,14 @@ NESTED BLOCKS
172
166
 
173
167
  .blockOuter:not([data-prev-type])
174
168
  > .block
175
- > .blockContent[data-list-item-type="ordered"]::before {
169
+ > .blockContent[data-content-type="numberedListItem"]::before {
176
170
  margin-right: 1.2em;
177
171
  content: var(--index)".";
178
172
  }
179
173
 
180
174
  /* Unordered */
181
175
  /* No list nesting */
182
- .blockOuter[data-prev-list-item-type="unordered"]
176
+ .blockOuter[data-prev-type="bulletListItem"]
183
177
  > .block
184
178
  > .blockContent::before {
185
179
  margin-right: 1.2em;
@@ -188,43 +182,43 @@ NESTED BLOCKS
188
182
 
189
183
  .blockOuter:not([data-prev-type])
190
184
  > .block
191
- > .blockContent[data-list-item-type="unordered"]::before {
185
+ > .blockContent[data-content-type="bulletListItem"]::before {
192
186
  margin-right: 1.2em;
193
187
  content: "•";
194
188
  }
195
189
 
196
190
  /* 1 level of list nesting */
197
- [data-list-item-type="unordered"]~.blockGroup
198
- > .blockOuter[data-prev-list-item-type="unordered"]
191
+ [data-content-type="bulletListItem"]~.blockGroup
192
+ > .blockOuter[data-prev-type="bulletListItem"]
199
193
  > .block
200
194
  > .blockContent::before {
201
195
  margin-right: 1.2em;
202
196
  content: "◦";
203
197
  }
204
198
 
205
- [data-list-item-type="unordered"]~.blockGroup
199
+ [data-content-type="bulletListItem"]~.blockGroup
206
200
  > .blockOuter:not([data-prev-type])
207
201
  > .block
208
- > .blockContent[data-list-item-type="unordered"]::before {
202
+ > .blockContent[data-content-type="bulletListItem"]::before {
209
203
  margin-right: 1.2em;
210
204
  content: "◦";
211
205
  }
212
206
 
213
207
  /* 2 levels of list nesting */
214
- [data-list-item-type="unordered"]~.blockGroup
215
- [data-list-item-type="unordered"]~.blockGroup
216
- > .blockOuter[data-prev-list-item-type="unordered"]
208
+ [data-content-type="bulletListItem"]~.blockGroup
209
+ [data-content-type="bulletListItem"]~.blockGroup
210
+ > .blockOuter[data-prev-type="bulletListItem"]
217
211
  > .block
218
212
  > .blockContent::before {
219
213
  margin-right: 1.2em;
220
214
  content: "▪";
221
215
  }
222
216
 
223
- [data-list-item-type="unordered"]~.blockGroup
224
- [data-list-item-type="unordered"]~.blockGroup
217
+ [data-content-type="bulletListItem"]~.blockGroup
218
+ [data-content-type="bulletListItem"]~.blockGroup
225
219
  > .blockOuter:not([data-prev-type])
226
220
  > .block
227
- > .blockContent[data-list-item-type="unordered"]::before {
221
+ > .blockContent[data-content-type="bulletListItem"]::before {
228
222
  margin-right: 1.2em;
229
223
  content: "▪";
230
224
  }
@@ -257,12 +251,105 @@ NESTED BLOCKS
257
251
  content: "Type to filter";
258
252
  }
259
253
 
260
- .blockContent[data-content-type="headingContent"].isEmpty
254
+ .blockContent[data-content-type="heading"].isEmpty
261
255
  > :first-child::before {
262
256
  content: "Heading";
263
257
  }
264
258
 
265
- .blockContent[data-content-type="listItemContent"].isEmpty
266
- > :first-child:before {
259
+ .blockContent[data-content-type="bulletListItem"].isEmpty
260
+ > :first-child:before,
261
+ .blockContent[data-content-type="numberedListItem"].isEmpty
262
+ > :first-child:before {
267
263
  content: "List";
268
264
  }
265
+
266
+ /* TEXT COLORS */
267
+ [data-text-color="gray"] {
268
+ color: #9b9a97;
269
+ }
270
+
271
+ [data-text-color="brown"] {
272
+ color: #64473a;
273
+ }
274
+
275
+ [data-text-color="red"] {
276
+ color: #e03e3e;
277
+ }
278
+
279
+ [data-text-color="orange"] {
280
+ color: #d9730d;
281
+ }
282
+
283
+ [data-text-color="yellow"] {
284
+ color: #dfab01;
285
+ }
286
+
287
+ [data-text-color="green"] {
288
+ color: #4d6461;
289
+ }
290
+
291
+ [data-text-color="blue"] {
292
+ color: #0b6e99;
293
+ }
294
+
295
+ [data-text-color="purple"] {
296
+ color: #6940a5;
297
+ }
298
+
299
+ [data-text-color="pink"] {
300
+ color: #ad1a72;
301
+ }
302
+
303
+ /* BACKGROUND COLORS */
304
+ [data-background-color="gray"] {
305
+ background-color: #ebeced;
306
+ }
307
+
308
+ [data-background-color="brown"] {
309
+ background-color: #e9e5e3;
310
+ }
311
+
312
+ [data-background-color="red"] {
313
+ background-color: #fbe4e4;
314
+ }
315
+
316
+ [data-background-color="orange"] {
317
+ background-color: #faebdd;
318
+ }
319
+
320
+ [data-background-color="yellow"] {
321
+ background-color: #fbf3db;
322
+ }
323
+
324
+ [data-background-color="green"] {
325
+ background-color: #ddedea;
326
+ }
327
+
328
+ [data-background-color="blue"] {
329
+ background-color: #ddebf1;
330
+ }
331
+
332
+ [data-background-color="purple"] {
333
+ background-color: #eae4f2;
334
+ }
335
+
336
+ [data-background-color="pink"] {
337
+ background-color: #f4dfeb;
338
+ }
339
+
340
+ /* TEXT ALIGNMENT */
341
+ [data-text-alignment="left"] {
342
+ text-align: left;
343
+ }
344
+
345
+ [data-text-alignment="center"] {
346
+ text-align: center;
347
+ }
348
+
349
+ [data-text-alignment="right"] {
350
+ text-align: right;
351
+ }
352
+
353
+ [data-text-alignment="justify"] {
354
+ text-align: justify;
355
+ }
@@ -1,23 +1,17 @@
1
1
  import { mergeAttributes, Node } from "@tiptap/core";
2
- import { Slice } from "prosemirror-model";
2
+ import { Fragment, Slice } from "prosemirror-model";
3
3
  import { TextSelection } from "prosemirror-state";
4
- import BlockAttributes from "../BlockAttributes";
4
+ import { BlockUpdate } from "../apiTypes";
5
5
  import { getBlockInfoFromPos } from "../helpers/getBlockInfoFromPos";
6
6
  import { PreviousBlockTypePlugin } from "../PreviousBlockTypePlugin";
7
7
  import styles from "./Block.module.css";
8
- import { TextContentType } from "./BlockTypes/TextBlock/TextContent";
9
- import { HeadingContentType } from "./BlockTypes/HeadingBlock/HeadingContent";
10
- import { ListItemContentType } from "./BlockTypes/ListItemBlock/ListItemContent";
8
+ import BlockAttributes from "./BlockAttributes";
11
9
 
10
+ // TODO
12
11
  export interface IBlock {
13
12
  HTMLAttributes: Record<string, any>;
14
13
  }
15
14
 
16
- export type BlockContentType =
17
- | TextContentType
18
- | HeadingContentType
19
- | ListItemContentType;
20
-
21
15
  declare module "@tiptap/core" {
22
16
  interface Commands<ReturnType> {
23
17
  block: {
@@ -25,13 +19,13 @@ declare module "@tiptap/core" {
25
19
  BNDeleteBlock: (posInBlock: number) => ReturnType;
26
20
  BNMergeBlocks: (posBetweenBlocks: number) => ReturnType;
27
21
  BNSplitBlock: (posInBlock: number, keepType: boolean) => ReturnType;
28
- BNSetContentType: (
22
+ BNUpdateBlock: (
29
23
  posInBlock: number,
30
- type: BlockContentType
24
+ blockUpdate: BlockUpdate
31
25
  ) => ReturnType;
32
- BNCreateBlockOrSetContentType: (
26
+ BNCreateOrUpdateBlock: (
33
27
  posInBlock: number,
34
- type: BlockContentType
28
+ blockUpdate: BlockUpdate
35
29
  ) => ReturnType;
36
30
  };
37
31
  }
@@ -40,9 +34,9 @@ declare module "@tiptap/core" {
40
34
  /**
41
35
  * The main "Block node" documents consist of
42
36
  */
43
- export const Block = Node.create<IBlock>({
44
- name: "block",
45
- group: "block",
37
+ export const BlockContainer = Node.create<IBlock>({
38
+ name: "blockContainer",
39
+ group: "blockContainer",
46
40
  // A block always contains content, and optionally a blockGroup which contains nested blocks
47
41
  content: "blockContent blockGroup?",
48
42
  // Ensures content-specific keyboard handlers trigger first.
@@ -55,17 +49,6 @@ export const Block = Node.create<IBlock>({
55
49
  };
56
50
  },
57
51
 
58
- addAttributes() {
59
- return {
60
- blockColor: {
61
- default: undefined,
62
- },
63
- blockStyle: {
64
- default: undefined,
65
- },
66
- };
67
- },
68
-
69
52
  parseHTML() {
70
53
  return [
71
54
  {
@@ -82,7 +65,7 @@ export const Block = Node.create<IBlock>({
82
65
  }
83
66
  }
84
67
 
85
- if (element.getAttribute("data-node-type") === "block") {
68
+ if (element.getAttribute("data-node-type") === "blockContainer") {
86
69
  return attrs;
87
70
  }
88
71
 
@@ -93,23 +76,15 @@ export const Block = Node.create<IBlock>({
93
76
  },
94
77
 
95
78
  renderHTML({ HTMLAttributes }) {
96
- const attrs: Record<string, string> = {};
97
- for (let [nodeAttr, HTMLAttr] of Object.entries(BlockAttributes)) {
98
- // Ensure falsy values are not misinterpreted.
99
- if (HTMLAttributes[nodeAttr] !== undefined) {
100
- attrs[HTMLAttr] = HTMLAttributes[nodeAttr];
101
- }
102
- }
103
-
104
79
  return [
105
80
  "div",
106
- mergeAttributes(attrs, {
81
+ mergeAttributes(HTMLAttributes, {
107
82
  class: styles.blockOuter,
108
83
  "data-node-type": "block-outer",
109
84
  }),
110
85
  [
111
86
  "div",
112
- mergeAttributes(attrs, {
87
+ mergeAttributes(HTMLAttributes, {
113
88
  // TODO: maybe remove html attributes from inner block
114
89
  class: styles.block,
115
90
  "data-node-type": this.name,
@@ -125,7 +100,8 @@ export const Block = Node.create<IBlock>({
125
100
  BNCreateBlock:
126
101
  (pos) =>
127
102
  ({ state, dispatch }) => {
128
- const newBlock = state.schema.nodes["block"].createAndFill()!;
103
+ const newBlock =
104
+ state.schema.nodes["blockContainer"].createAndFill()!;
129
105
 
130
106
  if (dispatch) {
131
107
  state.tr.insert(pos, newBlock);
@@ -176,10 +152,10 @@ export const Block = Node.create<IBlock>({
176
152
  ({ state, dispatch }) => {
177
153
  const nextNodeIsBlock =
178
154
  state.doc.resolve(posBetweenBlocks + 1).node().type.name ===
179
- "block";
155
+ "blockContainer";
180
156
  const prevNodeIsBlock =
181
157
  state.doc.resolve(posBetweenBlocks - 1).node().type.name ===
182
- "block";
158
+ "blockContainer";
183
159
 
184
160
  if (!nextNodeIsBlock || !prevNodeIsBlock) {
185
161
  return false;
@@ -246,19 +222,36 @@ export const Block = Node.create<IBlock>({
246
222
  const { contentNode, contentType, startPos, endPos, depth } =
247
223
  blockInfo;
248
224
 
249
- const newBlockInsertionPos = endPos + 1;
225
+ const originalBlockContent = state.doc.cut(startPos + 1, posInBlock);
226
+ const newBlockContent = state.doc.cut(posInBlock, endPos - 1);
250
227
 
251
- // Creates new block first, otherwise positions get changed due to the original block's content changing.
252
- // Only text content is transferred to the new block.
253
- const secondBlockContent = state.doc.textBetween(posInBlock, endPos);
228
+ const newBlock =
229
+ state.schema.nodes["blockContainer"].createAndFill()!;
254
230
 
255
- const newBlock = state.schema.nodes["block"].createAndFill()!;
231
+ const newBlockInsertionPos = endPos + 1;
256
232
  const newBlockContentPos = newBlockInsertionPos + 2;
257
233
 
258
234
  if (dispatch) {
235
+ // Creates a new block. Since the schema requires it to have a content node, a paragraph node is created
236
+ // automatically, spanning newBlockContentPos to newBlockContentPos + 1.
259
237
  state.tr.insert(newBlockInsertionPos, newBlock);
260
- state.tr.insertText(secondBlockContent, newBlockContentPos);
261
238
 
239
+ // Replaces the content of the newly created block's content node. Doesn't replace the whole content node so
240
+ // its type doesn't change.
241
+ state.tr.replace(
242
+ newBlockContentPos,
243
+ newBlockContentPos + 1,
244
+ newBlockContent.content.size > 0
245
+ ? new Slice(
246
+ Fragment.from(newBlockContent),
247
+ depth + 2,
248
+ depth + 2
249
+ )
250
+ : undefined
251
+ );
252
+
253
+ // Changes the type of the content node. The range doesn't matter as long as both from and to positions are
254
+ // within the content node.
262
255
  if (keepType) {
263
256
  state.tr.setBlockType(
264
257
  newBlockContentPos,
@@ -267,38 +260,49 @@ export const Block = Node.create<IBlock>({
267
260
  contentNode.attrs
268
261
  );
269
262
  }
270
- }
271
263
 
272
- // Updates content of original block.
273
- const firstBlockContent = state.doc.content.cut(startPos, posInBlock);
264
+ // Sets the selection to the start of the new block's content node.
265
+ state.tr.setSelection(
266
+ new TextSelection(state.doc.resolve(newBlockContentPos))
267
+ );
274
268
 
275
- if (dispatch) {
269
+ // Replaces the content of the original block's content node. Doesn't replace the whole content node so its
270
+ // type doesn't change.
276
271
  state.tr.replace(
277
- startPos,
278
- endPos,
279
- new Slice(firstBlockContent, depth, depth)
272
+ startPos + 1,
273
+ endPos - 1,
274
+ originalBlockContent.content.size > 0
275
+ ? new Slice(
276
+ Fragment.from(originalBlockContent),
277
+ depth + 2,
278
+ depth + 2
279
+ )
280
+ : undefined
280
281
  );
281
282
  }
282
283
 
283
284
  return true;
284
285
  },
285
- // Changes the content of a block at a given position to a given type.
286
- BNSetContentType:
287
- (posInBlock, type) =>
286
+ // Updates the type and attributes of a block at a given position.
287
+ BNUpdateBlock:
288
+ (posInBlock, blockUpdate) =>
288
289
  ({ state, dispatch }) => {
289
290
  const blockInfo = getBlockInfoFromPos(state.doc, posInBlock);
290
291
  if (blockInfo === undefined) {
291
292
  return false;
292
293
  }
293
294
 
294
- const { startPos, contentNode } = blockInfo;
295
+ const { node, startPos, contentNode } = blockInfo;
295
296
 
296
297
  if (dispatch) {
297
298
  state.tr.setBlockType(
298
299
  startPos + 1,
299
300
  startPos + contentNode.nodeSize + 1,
300
- state.schema.node(type.name).type,
301
- type.attrs
301
+ state.schema.node(blockUpdate.type).type,
302
+ {
303
+ ...node.attrs,
304
+ ...blockUpdate.props,
305
+ }
302
306
  );
303
307
  }
304
308
 
@@ -306,8 +310,8 @@ export const Block = Node.create<IBlock>({
306
310
  },
307
311
  // Changes the block at a given position to a given content type if it's empty, otherwise creates a new block of
308
312
  // that type below it.
309
- BNCreateBlockOrSetContentType:
310
- (posInBlock, type) =>
313
+ BNCreateOrUpdateBlock:
314
+ (posInBlock, blockUpdate) =>
311
315
  ({ state, chain }) => {
312
316
  const blockInfo = getBlockInfoFromPos(state.doc, posInBlock);
313
317
  if (blockInfo === undefined) {
@@ -320,7 +324,7 @@ export const Block = Node.create<IBlock>({
320
324
  const oldBlockContentPos = startPos + 1;
321
325
 
322
326
  return chain()
323
- .BNSetContentType(posInBlock, type)
327
+ .BNUpdateBlock(posInBlock, blockUpdate)
324
328
  .setTextSelection(oldBlockContentPos)
325
329
  .run();
326
330
  } else {
@@ -329,7 +333,7 @@ export const Block = Node.create<IBlock>({
329
333
 
330
334
  return chain()
331
335
  .BNCreateBlock(newBlockInsertionPos)
332
- .BNSetContentType(newBlockContentPos, type)
336
+ .BNUpdateBlock(newBlockContentPos, blockUpdate)
333
337
  .setTextSelection(newBlockContentPos)
334
338
  .run();
335
339
  }
@@ -349,7 +353,7 @@ export const Block = Node.create<IBlock>({
349
353
  () => commands.deleteSelection(),
350
354
  // Undoes an input rule if one was triggered in the last editor state change.
351
355
  () => commands.undoInputRule(),
352
- // Changes block type to a text block if it's not already, while the selection is at the start of the block.
356
+ // Reverts block content type to a paragraph if the selection is at the start of the block.
353
357
  () =>
354
358
  commands.command(({ state }) => {
355
359
  const { contentType } = getBlockInfoFromPos(
@@ -359,11 +363,12 @@ export const Block = Node.create<IBlock>({
359
363
 
360
364
  const selectionAtBlockStart =
361
365
  state.selection.$anchor.parentOffset === 0;
362
- const isTextBlock = contentType.name === "textContent";
366
+ const isParagraph = contentType.name === "paragraph";
363
367
 
364
- if (selectionAtBlockStart && !isTextBlock) {
365
- return commands.BNSetContentType(state.selection.from, {
366
- name: "textContent",
368
+ if (selectionAtBlockStart && !isParagraph) {
369
+ return commands.BNUpdateBlock(state.selection.from, {
370
+ type: "paragraph",
371
+ props: {},
367
372
  });
368
373
  }
369
374
 
@@ -376,7 +381,7 @@ export const Block = Node.create<IBlock>({
376
381
  state.selection.$anchor.parentOffset === 0;
377
382
 
378
383
  if (selectionAtBlockStart) {
379
- return commands.liftListItem("block");
384
+ return commands.liftListItem("blockContainer");
380
385
  }
381
386
 
382
387
  return false;
@@ -435,7 +440,7 @@ export const Block = Node.create<IBlock>({
435
440
  blockEmpty &&
436
441
  blockIndented
437
442
  ) {
438
- return commands.liftListItem("block");
443
+ return commands.liftListItem("blockContainer");
439
444
  }
440
445
 
441
446
  return false;
@@ -496,62 +501,51 @@ export const Block = Node.create<IBlock>({
496
501
  return {
497
502
  Backspace: handleBackspace,
498
503
  Enter: handleEnter,
499
- Tab: () => this.editor.commands.sinkListItem("block"),
500
- "Shift-Tab": () => this.editor.commands.liftListItem("block"),
504
+ // Always returning true for tab key presses ensures they're not captured by the browser. Otherwise, they blur the
505
+ // editor since the browser will try to use tab for keyboard navigation.
506
+ Tab: () => {
507
+ this.editor.commands.sinkListItem("blockContainer");
508
+ return true;
509
+ },
510
+ "Shift-Tab": () => {
511
+ this.editor.commands.liftListItem("blockContainer");
512
+ return true;
513
+ },
501
514
  "Mod-Alt-0": () =>
502
515
  this.editor.commands.BNCreateBlock(
503
516
  this.editor.state.selection.anchor + 2
504
517
  ),
505
518
  "Mod-Alt-1": () =>
506
- this.editor.commands.BNSetContentType(
507
- this.editor.state.selection.anchor,
508
- {
509
- name: "headingContent",
510
- attrs: {
511
- headingLevel: "1",
512
- },
513
- }
514
- ),
519
+ this.editor.commands.BNUpdateBlock(this.editor.state.selection.anchor, {
520
+ type: "heading",
521
+ props: {
522
+ level: "1",
523
+ },
524
+ }),
515
525
  "Mod-Alt-2": () =>
516
- this.editor.commands.BNSetContentType(
517
- this.editor.state.selection.anchor,
518
- {
519
- name: "headingContent",
520
- attrs: {
521
- headingLevel: "2",
522
- },
523
- }
524
- ),
526
+ this.editor.commands.BNUpdateBlock(this.editor.state.selection.anchor, {
527
+ type: "heading",
528
+ props: {
529
+ level: "2",
530
+ },
531
+ }),
525
532
  "Mod-Alt-3": () =>
526
- this.editor.commands.BNSetContentType(
527
- this.editor.state.selection.anchor,
528
- {
529
- name: "headingContent",
530
- attrs: {
531
- headingLevel: "3",
532
- },
533
- }
534
- ),
533
+ this.editor.commands.BNUpdateBlock(this.editor.state.selection.anchor, {
534
+ type: "heading",
535
+ props: {
536
+ level: "3",
537
+ },
538
+ }),
535
539
  "Mod-Shift-7": () =>
536
- this.editor.commands.BNSetContentType(
537
- this.editor.state.selection.anchor,
538
- {
539
- name: "listItemContent",
540
- attrs: {
541
- listItemType: "unordered",
542
- },
543
- }
544
- ),
540
+ this.editor.commands.BNUpdateBlock(this.editor.state.selection.anchor, {
541
+ type: "bulletListItem",
542
+ props: {},
543
+ }),
545
544
  "Mod-Shift-8": () =>
546
- this.editor.commands.BNSetContentType(
547
- this.editor.state.selection.anchor,
548
- {
549
- name: "listItemContent",
550
- attrs: {
551
- listItemType: "ordered",
552
- },
553
- }
554
- ),
545
+ this.editor.commands.BNUpdateBlock(this.editor.state.selection.anchor, {
546
+ type: "numberedListItem",
547
+ props: {},
548
+ }),
555
549
  };
556
550
  },
557
551
  });