@blocknote/core 0.4.3 → 0.4.5

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "homepage": "https://github.com/yousefed/blocknote",
4
4
  "private": false,
5
5
  "license": "MPL-2.0",
6
- "version": "0.4.3",
6
+ "version": "0.4.5",
7
7
  "files": [
8
8
  "dist",
9
9
  "types",
@@ -106,5 +106,5 @@
106
106
  "access": "public",
107
107
  "registry": "https://registry.npmjs.org/"
108
108
  },
109
- "gitHead": "4f578e0a8676b815316bdeb978cd4748d948868c"
109
+ "gitHead": "c0d8a428dde4af76d16335a8da7f0e0ae1294eaf"
110
110
  }
@@ -1,30 +1,34 @@
1
1
  import { Editor, EditorOptions } from "@tiptap/core";
2
2
  import { Node } from "prosemirror-model";
3
3
  // import "./blocknote.css";
4
- import { Block, PartialBlock } from "./extensions/Blocks/api/blockTypes";
5
- import { getBlockNoteExtensions, UiFactories } from "./BlockNoteExtensions";
6
- import styles from "./editor.module.css";
7
- import {
8
- defaultSlashMenuItems,
9
- BaseSlashMenuItem,
10
- } from "./extensions/SlashMenu";
11
4
  import { Editor as TiptapEditor } from "@tiptap/core/dist/packages/core/src/Editor";
12
- import { nodeToBlock } from "./api/nodeConversions/nodeConversions";
13
- import { TextCursorPosition } from "./extensions/Blocks/api/cursorPositionTypes";
14
- import { getBlockInfoFromPos } from "./extensions/Blocks/helpers/getBlockInfoFromPos";
15
- import { getNodeById } from "./api/util/nodeUtil";
16
5
  import {
17
6
  insertBlocks,
18
- updateBlock,
19
7
  removeBlocks,
20
8
  replaceBlocks,
9
+ updateBlock,
21
10
  } from "./api/blockManipulation/blockManipulation";
22
11
  import {
23
12
  blocksToHTML,
24
- HTMLToBlocks,
25
13
  blocksToMarkdown,
14
+ HTMLToBlocks,
26
15
  markdownToBlocks,
27
16
  } from "./api/formatConversions/formatConversions";
17
+ import { nodeToBlock } from "./api/nodeConversions/nodeConversions";
18
+ import { getNodeById } from "./api/util/nodeUtil";
19
+ import { getBlockNoteExtensions, UiFactories } from "./BlockNoteExtensions";
20
+ import styles from "./editor.module.css";
21
+ import {
22
+ Block,
23
+ BlockIdentifier,
24
+ PartialBlock,
25
+ } from "./extensions/Blocks/api/blockTypes";
26
+ import { TextCursorPosition } from "./extensions/Blocks/api/cursorPositionTypes";
27
+ import { getBlockInfoFromPos } from "./extensions/Blocks/helpers/getBlockInfoFromPos";
28
+ import {
29
+ BaseSlashMenuItem,
30
+ defaultSlashMenuItems,
31
+ } from "./extensions/SlashMenu";
28
32
 
29
33
  export type BlockNoteEditorOptions = {
30
34
  // TODO: Figure out if enableBlockNoteExtensions/disableHistoryExtension are needed and document them.
@@ -34,8 +38,9 @@ export type BlockNoteEditorOptions = {
34
38
  slashCommands: BaseSlashMenuItem[];
35
39
  parentElement: HTMLElement;
36
40
  editorDOMAttributes: Record<string, string>;
37
- onUpdate: (editor: BlockNoteEditor) => void;
38
- onCreate: (editor: BlockNoteEditor) => void;
41
+ onEditorCreate: (editor: BlockNoteEditor) => void;
42
+ onEditorContentChange: (editor: BlockNoteEditor) => void;
43
+ onTextCursorPositionChange: (editor: BlockNoteEditor) => void;
39
44
 
40
45
  // tiptap options, undocumented
41
46
  _tiptapOptions: any;
@@ -69,11 +74,14 @@ export class BlockNoteEditor {
69
74
  const tiptapOptions: EditorOptions = {
70
75
  ...blockNoteTipTapOptions,
71
76
  ...options._tiptapOptions,
77
+ onCreate: () => {
78
+ options.onEditorCreate?.(this);
79
+ },
72
80
  onUpdate: () => {
73
- options.onUpdate?.(this);
81
+ options.onEditorContentChange?.(this);
74
82
  },
75
- onCreate: () => {
76
- options.onCreate?.(this);
83
+ onSelectionUpdate: () => {
84
+ options.onTextCursorPositionChange?.(this);
77
85
  },
78
86
  extensions:
79
87
  options.enableBlockNoteExtensions === false
@@ -91,13 +99,18 @@ export class BlockNoteEditor {
91
99
  },
92
100
  };
93
101
 
102
+ if (options.parentElement) {
103
+ tiptapOptions.element = options.parentElement;
104
+ }
105
+
94
106
  this._tiptapEditor = new Editor(tiptapOptions) as Editor & {
95
107
  contentComponent: any;
96
108
  };
97
109
  }
98
110
 
99
111
  /**
100
- * Gets a list of all top-level blocks that are in the editor.
112
+ * Gets a snapshot of all top-level (non-nested) blocks in the editor.
113
+ * @returns A snapshot of all top-level (non-nested) blocks in the editor.
101
114
  */
102
115
  public get topLevelBlocks(): Block[] {
103
116
  const blocks: Block[] = [];
@@ -112,12 +125,40 @@ export class BlockNoteEditor {
112
125
  }
113
126
 
114
127
  /**
115
- * Traverses all blocks in the editor, including all nested blocks, and executes a callback for each. The traversal is
116
- * depth-first, which is the same order as blocks appear in the editor by y-coordinate.
117
- * @param callback The callback to execute for each block.
128
+ * Gets a snapshot of an existing block from the editor.
129
+ * @param blockIdentifier The identifier of an existing block that should be retrieved.
130
+ * @returns The block that matches the identifier, or `undefined` if no matching block was found.
131
+ */
132
+ public getBlock(blockIdentifier: BlockIdentifier): Block | undefined {
133
+ const id =
134
+ typeof blockIdentifier === "string"
135
+ ? blockIdentifier
136
+ : blockIdentifier.id;
137
+ let newBlock: Block | undefined = undefined;
138
+
139
+ this._tiptapEditor.state.doc.firstChild!.descendants((node) => {
140
+ if (typeof newBlock !== "undefined") {
141
+ return false;
142
+ }
143
+
144
+ if (node.type.name !== "blockContainer" || node.attrs.id !== id) {
145
+ return true;
146
+ }
147
+
148
+ newBlock = nodeToBlock(node, this.blockCache);
149
+
150
+ return false;
151
+ });
152
+
153
+ return newBlock;
154
+ }
155
+
156
+ /**
157
+ * Traverses all blocks in the editor depth-first, and executes a callback for each.
158
+ * @param callback The callback to execute for each block. Returning `false` stops the traversal.
118
159
  * @param reverse Whether the blocks should be traversed in reverse order.
119
160
  */
120
- public allBlocks(
161
+ public forEachBlock(
121
162
  callback: (block: Block) => void,
122
163
  reverse: boolean = false
123
164
  ): void {
@@ -139,7 +180,8 @@ export class BlockNoteEditor {
139
180
  }
140
181
 
141
182
  /**
142
- * Gets information regarding the position of the text cursor in the editor.
183
+ * Gets a snapshot of the current text cursor position.
184
+ * @returns A snapshot of the current text cursor position.
143
185
  */
144
186
  public getTextCursorPosition(): TextCursorPosition {
145
187
  const { node, depth, startPos, endPos } = getBlockInfoFromPos(
@@ -181,14 +223,19 @@ export class BlockNoteEditor {
181
223
  };
182
224
  }
183
225
 
226
+ /**
227
+ * Sets the text cursor position to the start or end of an existing block. Throws an error if the target block could
228
+ * not be found.
229
+ * @param targetBlock The identifier of an existing block that the text cursor should be moved to.
230
+ * @param placement Whether the text cursor should be placed at the start or end of the block.
231
+ */
184
232
  public setTextCursorPosition(
185
- block: Block,
233
+ targetBlock: BlockIdentifier,
186
234
  placement: "start" | "end" = "start"
187
235
  ) {
188
- const { posBeforeNode } = getNodeById(
189
- block.id,
190
- this._tiptapEditor.state.doc
191
- );
236
+ const id = typeof targetBlock === "string" ? targetBlock : targetBlock.id;
237
+
238
+ const { posBeforeNode } = getNodeById(id, this._tiptapEditor.state.doc);
192
239
  const { startPos, contentNode } = getBlockInfoFromPos(
193
240
  this._tiptapEditor.state.doc,
194
241
  posBeforeNode + 2
@@ -204,48 +251,46 @@ export class BlockNoteEditor {
204
251
  }
205
252
 
206
253
  /**
207
- * Inserts multiple blocks before, after, or nested inside an existing block in the editor.
208
- * @param blocksToInsert An array of blocks to insert.
209
- * @param blockToInsertAt An existing block, marking where the new blocks should be inserted at.
210
- * @param placement Determines whether the blocks should be inserted just before, just after, or nested inside the
211
- * existing block.
254
+ * Inserts new blocks into the editor. If a block's `id` is undefined, BlockNote generates one automatically. Throws an
255
+ * error if the reference block could not be found.
256
+ * @param blocksToInsert An array of partial blocks that should be inserted.
257
+ * @param referenceBlock An identifier for an existing block, at which the new blocks should be inserted.
258
+ * @param placement Whether the blocks should be inserted just before, just after, or nested inside the
259
+ * `referenceBlock`. Inserts the blocks at the start of the existing block's children if "nested" is used.
212
260
  */
213
261
  public insertBlocks(
214
262
  blocksToInsert: PartialBlock[],
215
- blockToInsertAt: Block,
263
+ referenceBlock: BlockIdentifier,
216
264
  placement: "before" | "after" | "nested" = "before"
217
265
  ): void {
218
- insertBlocks(
219
- blocksToInsert,
220
- blockToInsertAt,
221
- placement,
222
- this._tiptapEditor
223
- );
266
+ insertBlocks(blocksToInsert, referenceBlock, placement, this._tiptapEditor);
224
267
  }
225
268
 
226
269
  /**
227
- * Updates a block in the editor to the given specification.
270
+ * Updates an existing block in the editor. Since updatedBlock is a PartialBlock object, some fields might not be
271
+ * defined. These undefined fields are kept as-is from the existing block. Throws an error if the block to update could
272
+ * not be found.
228
273
  * @param blockToUpdate The block that should be updated.
229
- * @param updatedBlock The specification that the block should be updated to.
274
+ * @param update A partial block which defines how the existing block should be changed.
230
275
  */
231
- public updateBlock(blockToUpdate: Block, updatedBlock: PartialBlock) {
232
- updateBlock(blockToUpdate, updatedBlock, this._tiptapEditor);
276
+ public updateBlock(blockToUpdate: Block, update: PartialBlock) {
277
+ updateBlock(blockToUpdate, update, this._tiptapEditor);
233
278
  }
234
279
 
235
280
  /**
236
- * Removes multiple blocks from the editor. Throws an error if any of the blocks could not be found.
237
- * @param blocksToRemove An array of blocks that should be removed.
281
+ * Removes existing blocks from the editor. Throws an error if any of the blocks could not be found.
282
+ * @param blocksToRemove An array of identifiers for existing blocks that should be removed.
238
283
  */
239
284
  public removeBlocks(blocksToRemove: Block[]) {
240
285
  removeBlocks(blocksToRemove, this._tiptapEditor);
241
286
  }
242
287
 
243
288
  /**
244
- * Replaces multiple blocks in the editor with several other blocks. If the provided blocks to remove are not adjacent
245
- * to each other, the new blocks are inserted at the position of the first block in the array. Throws an error if any
246
- * of the blocks could not be found.
289
+ * Replaces existing blocks in the editor with new blocks. If the blocks that should be removed are not adjacent or
290
+ * are at different nesting levels, `blocksToInsert` will be inserted at the position of the first block in
291
+ * `blocksToRemove`. Throws an error if any of the blocks to remove could not be found.
247
292
  * @param blocksToRemove An array of blocks that should be replaced.
248
- * @param blocksToInsert An array of blocks to replace the old ones with.
293
+ * @param blocksToInsert An array of partial blocks to replace the old ones with.
249
294
  */
250
295
  public replaceBlocks(
251
296
  blocksToRemove: Block[],
@@ -255,46 +300,44 @@ export class BlockNoteEditor {
255
300
  }
256
301
 
257
302
  /**
258
- * Executes a callback function whenever the editor's content changes.
259
- * @param callback The callback function to execute.
260
- */
261
- public onContentChange(callback: () => void) {
262
- this._tiptapEditor.on("update", callback);
263
- }
264
-
265
- /**
266
- * Serializes a list of blocks into an HTML string. The output is not the same as what's rendered by the editor, and
267
- * is simplified in order to better conform to HTML standards. Block structuring elements are removed, children of
268
- * blocks which aren't list items are lifted out of them, and list items blocks are wrapped in `ul`/`ol` tags.
269
- * @param blocks The list of blocks to serialize into HTML.
303
+ * Serializes blocks into an HTML string. To better conform to HTML standards, children of blocks which aren't list
304
+ * items are un-nested in the output HTML.
305
+ * @param blocks An array of blocks that should be serialized into HTML.
306
+ * @returns The blocks, serialized as an HTML string.
270
307
  */
271
308
  public async blocksToHTML(blocks: Block[]): Promise<string> {
272
309
  return blocksToHTML(blocks, this._tiptapEditor.schema);
273
310
  }
274
311
 
275
312
  /**
276
- * Creates a list of blocks from an HTML string.
277
- * @param htmlString The HTML string to create a list of blocks from.
313
+ * Parses blocks from an HTML string. Tries to create `Block` objects out of any HTML block-level elements, and
314
+ * `InlineNode` objects from any HTML inline elements, though not all element types are recognized. If BlockNote
315
+ * doesn't recognize an HTML element's tag, it will parse it as a paragraph or plain text.
316
+ * @param html The HTML string to parse blocks from.
317
+ * @returns The blocks parsed from the HTML string.
278
318
  */
279
- public async HTMLToBlocks(htmlString: string): Promise<Block[]> {
280
- return HTMLToBlocks(htmlString, this._tiptapEditor.schema);
319
+ public async HTMLToBlocks(html: string): Promise<Block[]> {
320
+ return HTMLToBlocks(html, this._tiptapEditor.schema);
281
321
  }
282
322
 
283
323
  /**
284
- * Serializes a list of blocks into a Markdown string. The output is simplified as Markdown does not support all
285
- * features of BlockNote. Block structuring elements are removed, children of blocks which aren't list items are
286
- * lifted out of them, and certain styles are removed.
287
- * @param blocks The list of blocks to serialize into Markdown.
324
+ * Serializes blocks into a Markdown string. The output is simplified as Markdown does not support all features of
325
+ * BlockNote - children of blocks which aren't list items are un-nested and certain styles are removed.
326
+ * @param blocks An array of blocks that should be serialized into Markdown.
327
+ * @returns The blocks, serialized as a Markdown string.
288
328
  */
289
329
  public async blocksToMarkdown(blocks: Block[]): Promise<string> {
290
330
  return blocksToMarkdown(blocks, this._tiptapEditor.schema);
291
331
  }
292
332
 
293
333
  /**
294
- * Creates a list of blocks from a Markdown string.
295
- * @param markdownString The Markdown string to create a list of blocks from.
334
+ * Creates a list of blocks from a Markdown string. Tries to create `Block` and `InlineNode` objects based on
335
+ * Markdown syntax, though not all symbols are recognized. If BlockNote doesn't recognize a symbol, it will parse it
336
+ * as text.
337
+ * @param markdown The Markdown string to parse blocks from.
338
+ * @returns The blocks parsed from the Markdown string.
296
339
  */
297
- public async markdownToBlocks(markdownString: string): Promise<Block[]> {
298
- return markdownToBlocks(markdownString, this._tiptapEditor.schema);
340
+ public async markdownToBlocks(markdown: string): Promise<Block[]> {
341
+ return markdownToBlocks(markdown, this._tiptapEditor.schema);
299
342
  }
300
343
  }
@@ -38,11 +38,11 @@ export async function blocksToHTML(
38
38
  }
39
39
 
40
40
  export async function HTMLToBlocks(
41
- htmlString: string,
41
+ html: string,
42
42
  schema: Schema
43
43
  ): Promise<Block[]> {
44
44
  const htmlNode = document.createElement("div");
45
- htmlNode.innerHTML = htmlString.trim();
45
+ htmlNode.innerHTML = html.trim();
46
46
 
47
47
  const parser = DOMParser.fromSchema(schema);
48
48
  const parentNode = parser.parse(htmlNode);
@@ -72,7 +72,7 @@ export async function blocksToMarkdown(
72
72
  }
73
73
 
74
74
  export async function markdownToBlocks(
75
- markdownString: string,
75
+ markdown: string,
76
76
  schema: Schema
77
77
  ): Promise<Block[]> {
78
78
  const htmlString = await unified()
@@ -80,7 +80,7 @@ export async function markdownToBlocks(
80
80
  .use(remarkGfm)
81
81
  .use(remarkRehype)
82
82
  .use(rehypeStringify)
83
- .process(markdownString);
83
+ .process(markdown);
84
84
 
85
85
  return HTMLToBlocks(htmlString.value as string, schema);
86
86
  }
@@ -110,10 +110,10 @@ export const BlockContainer = Node.create<IBlock>({
110
110
 
111
111
  return true;
112
112
  },
113
- // Deletes a block at a given position and sets the selection to where the block was.
113
+ // Deletes a block at a given position.
114
114
  BNDeleteBlock:
115
115
  (posInBlock) =>
116
- ({ state, view, dispatch }) => {
116
+ ({ state, dispatch }) => {
117
117
  const blockInfo = getBlockInfoFromPos(state.doc, posInBlock);
118
118
  if (blockInfo === undefined) {
119
119
  return false;
@@ -123,10 +123,89 @@ export const BlockContainer = Node.create<IBlock>({
123
123
 
124
124
  if (dispatch) {
125
125
  state.tr.deleteRange(startPos, endPos);
126
- state.tr.setSelection(
127
- new TextSelection(state.doc.resolve(startPos + 1))
126
+ }
127
+
128
+ return true;
129
+ },
130
+ // Updates a block at a given position.
131
+ BNUpdateBlock:
132
+ (posInBlock, block) =>
133
+ ({ state, dispatch }) => {
134
+ const blockInfo = getBlockInfoFromPos(state.doc, posInBlock);
135
+ if (blockInfo === undefined) {
136
+ return false;
137
+ }
138
+
139
+ const { startPos, endPos, node, contentNode } = blockInfo;
140
+
141
+ if (dispatch) {
142
+ // Adds blockGroup node with child blocks if necessary.
143
+ if (block.children !== undefined) {
144
+ const childNodes = [];
145
+
146
+ // Creates ProseMirror nodes for each child block, including their descendants.
147
+ for (const child of block.children) {
148
+ childNodes.push(blockToNode(child, state.schema));
149
+ }
150
+
151
+ // Checks if a blockGroup node already exists.
152
+ if (node.childCount === 2) {
153
+ // Replaces all child nodes in the existing blockGroup with the ones created earlier.
154
+ state.tr.replace(
155
+ startPos + contentNode.nodeSize + 1,
156
+ endPos - 1,
157
+ new Slice(Fragment.from(childNodes), 0, 0)
158
+ );
159
+ } else {
160
+ // Inserts a new blockGroup containing the child nodes created earlier.
161
+ state.tr.insert(
162
+ startPos + contentNode.nodeSize,
163
+ state.schema.nodes["blockGroup"].create({}, childNodes)
164
+ );
165
+ }
166
+ }
167
+
168
+ // Replaces the blockContent node's content if necessary.
169
+ if (block.content !== undefined) {
170
+ let content: PMNode[] = [];
171
+
172
+ // Checks if the provided content is a string or InlineContent[] type.
173
+ if (typeof block.content === "string") {
174
+ // Adds a single text node with no marks to the content.
175
+ content.push(state.schema.text(block.content));
176
+ } else {
177
+ // Adds a text node with the provided styles converted into marks to the content, for each InlineContent
178
+ // object.
179
+ content = inlineContentToNodes(block.content, state.schema);
180
+ }
181
+
182
+ // Replaces the contents of the blockContent node with the previously created text node(s).
183
+ state.tr.replace(
184
+ startPos + 1,
185
+ startPos + contentNode.nodeSize - 1,
186
+ new Slice(Fragment.from(content), 0, 0)
187
+ );
188
+ }
189
+
190
+ // Changes the blockContent node type and adds the provided props as attributes. Also preserves all existing
191
+ // attributes that are compatible with the new type.
192
+ state.tr.setNodeMarkup(
193
+ startPos,
194
+ block.type === undefined
195
+ ? undefined
196
+ : state.schema.nodes[block.type],
197
+ {
198
+ ...contentNode.attrs,
199
+ ...block.props,
200
+ }
128
201
  );
129
- view.focus();
202
+
203
+ // Adds all provided props as attributes to the parent blockContainer node too, and also preserves existing
204
+ // attributes.
205
+ state.tr.setNodeMarkup(startPos - 1, undefined, {
206
+ ...node.attrs,
207
+ ...block.props,
208
+ });
130
209
  }
131
210
 
132
211
  return true;
@@ -283,112 +362,6 @@ export const BlockContainer = Node.create<IBlock>({
283
362
 
284
363
  return true;
285
364
  },
286
- // Updates a block to the given specification.
287
- BNUpdateBlock:
288
- (posInBlock, block) =>
289
- ({ state, dispatch }) => {
290
- const blockInfo = getBlockInfoFromPos(state.doc, posInBlock);
291
- if (blockInfo === undefined) {
292
- return false;
293
- }
294
-
295
- const { startPos, endPos, node, contentNode } = blockInfo;
296
-
297
- if (dispatch) {
298
- // Adds blockGroup node with child blocks if necessary.
299
- if (block.children !== undefined) {
300
- const childNodes = [];
301
-
302
- // Creates ProseMirror nodes for each child block, including their descendants.
303
- for (const child of block.children) {
304
- childNodes.push(blockToNode(child, state.schema));
305
- }
306
-
307
- // Checks if a blockGroup node already exists.
308
- if (node.childCount === 2) {
309
- // Replaces all child nodes in the existing blockGroup with the ones created earlier.
310
- state.tr.replace(
311
- startPos + contentNode.nodeSize + 1,
312
- endPos - 1,
313
- new Slice(Fragment.from(childNodes), 0, 0)
314
- );
315
- } else {
316
- // Inserts a new blockGroup containing the child nodes created earlier.
317
- state.tr.insert(
318
- startPos + contentNode.nodeSize,
319
- state.schema.nodes["blockGroup"].create({}, childNodes)
320
- );
321
- }
322
- }
323
-
324
- // Replaces the blockContent node's content if necessary.
325
- if (block.content !== undefined) {
326
- let content: PMNode[] = [];
327
-
328
- // Checks if the provided content is a string or InlineContent[] type.
329
- if (typeof block.content === "string") {
330
- // Adds a single text node with no marks to the content.
331
- content.push(state.schema.text(block.content));
332
- } else {
333
- // Adds a text node with the provided styles converted into marks to the content, for each InlineContent
334
- // object.
335
- content = inlineContentToNodes(block.content, state.schema);
336
- }
337
-
338
- // Replaces the contents of the blockContent node with the previously created text node(s).
339
- state.tr.replace(
340
- startPos + 1,
341
- startPos + contentNode.nodeSize - 1,
342
- new Slice(Fragment.from(content), 0, 0)
343
- );
344
- }
345
-
346
- // Changes the block type and adds the provided props as node attributes. Also preserves all existing node
347
- // attributes that are compatible with the new type.
348
- state.tr.setNodeMarkup(
349
- startPos,
350
- block.type === undefined
351
- ? undefined
352
- : state.schema.nodes[block.type],
353
- {
354
- ...contentNode.attrs,
355
- ...block.props,
356
- }
357
- );
358
- }
359
-
360
- return true;
361
- },
362
- // Updates a block to the given specification if it's empty, otherwise creates a new block from that specification
363
- // below it.
364
- BNCreateOrUpdateBlock:
365
- (posInBlock, block) =>
366
- ({ state, chain }) => {
367
- const blockInfo = getBlockInfoFromPos(state.doc, posInBlock);
368
- if (blockInfo === undefined) {
369
- return false;
370
- }
371
-
372
- const { node, startPos, endPos } = blockInfo;
373
-
374
- if (node.textContent.length === 0) {
375
- const oldBlockContentPos = startPos + 1;
376
-
377
- return chain()
378
- .BNUpdateBlock(posInBlock, block)
379
- .setTextSelection(oldBlockContentPos)
380
- .run();
381
- } else {
382
- const newBlockInsertionPos = endPos + 1;
383
- const newBlockContentPos = newBlockInsertionPos + 1;
384
-
385
- return chain()
386
- .BNCreateBlock(newBlockInsertionPos)
387
- .BNUpdateBlock(newBlockContentPos, block)
388
- .setTextSelection(newBlockContentPos)
389
- .run();
390
- }
391
- },
392
365
  };
393
366
  },
394
367