@reiwuzen/blocky 1.3.0 → 1.3.2

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/index.cjs CHANGED
@@ -21,10 +21,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  applyMarkdownTransform: () => applyMarkdownTransform,
24
- blockDeleteLastChar: () => blockDeleteLastChar,
25
- blockDeleteRange: () => blockDeleteRange,
26
- blockInsertAt: () => blockInsertAt,
27
- blockReplaceRange: () => blockReplaceRange,
28
24
  canRedo: () => canRedo,
29
25
  canUndo: () => canUndo,
30
26
  changeBlockType: () => changeBlockType,
@@ -39,8 +35,6 @@ __export(index_exports, {
39
35
  deserializeNodes: () => deserializeNodes,
40
36
  duplicateBlock: () => duplicateBlock,
41
37
  duplicateBlockAfter: () => duplicateBlockAfter,
42
- flatToPosition: () => flatToPosition,
43
- flatToSelection: () => flatToSelection,
44
38
  formatNodes: () => formatNodes,
45
39
  generateId: () => generateId,
46
40
  indentBlock: () => indentBlock,
@@ -50,7 +44,6 @@ __export(index_exports, {
50
44
  mergeBlocks: () => mergeBlocks,
51
45
  moveBlock: () => moveBlock,
52
46
  outdentBlock: () => outdentBlock,
53
- positionToFlat: () => positionToFlat,
54
47
  push: () => push,
55
48
  redo: () => redo,
56
49
  removeLink: () => removeLink,
@@ -72,7 +65,7 @@ __export(index_exports, {
72
65
  });
73
66
  module.exports = __toCommonJS(index_exports);
74
67
 
75
- // src/utils/block.ts
68
+ // src/engine/block.ts
76
69
  var import_result = require("@reiwuzen/result");
77
70
  var import_uuid = require("uuid");
78
71
  function generateId(fn) {
@@ -468,25 +461,56 @@ function splitBlock(block, nodeIndex, offset) {
468
461
  const targetLen = target ? getTextLength(target) : 0;
469
462
  if (offset < 0 || offset > targetLen)
470
463
  return import_result3.Result.Err(`offset (${offset}) out of bounds`);
464
+ const atStart = nodeIndex === 0 && offset === 0;
465
+ const atEnd = nodeIndex === nodes.length - 1 && offset === targetLen;
471
466
  const beforeNodes = [
472
467
  ...nodes.slice(0, nodeIndex),
473
- ...target && offset > 0 ? [sliceNode(target, 0, offset)].filter(Boolean) : []
468
+ ...target && offset > 0 ? [sliceNode(target, 0, offset)].filter(isNode) : []
474
469
  ];
475
470
  const afterNodes = [
476
- ...target && offset < targetLen ? [sliceNode(target, offset, targetLen)].filter(Boolean) : [],
471
+ ...target && offset < targetLen ? [sliceNode(target, offset, targetLen)].filter(isNode) : [],
477
472
  ...nodes.slice(nodeIndex + 1)
478
473
  ];
479
- const original = {
474
+ if (atStart) {
475
+ const left2 = {
476
+ ...block,
477
+ content: []
478
+ };
479
+ const right2 = {
480
+ id: generateId(),
481
+ type: block.type,
482
+ content: nodes,
483
+ meta: {}
484
+ };
485
+ return import_result3.Result.Ok([left2, right2]);
486
+ }
487
+ if (atEnd) {
488
+ const left2 = {
489
+ ...block,
490
+ content: nodes
491
+ };
492
+ const right2 = {
493
+ id: generateId(),
494
+ type: "paragraph",
495
+ content: [],
496
+ meta: {}
497
+ };
498
+ return import_result3.Result.Ok([left2, right2]);
499
+ }
500
+ const left = {
480
501
  ...block,
481
502
  content: beforeNodes
482
503
  };
483
- const newBlock = {
504
+ const right = {
484
505
  id: generateId(),
485
506
  type: "paragraph",
486
507
  content: afterNodes,
487
508
  meta: {}
488
509
  };
489
- return import_result3.Result.Ok([original, newBlock]);
510
+ return import_result3.Result.Ok([left, right]);
511
+ }
512
+ function isNode(node) {
513
+ return node !== null;
490
514
  }
491
515
  function mergeBlocks(blockA, blockB) {
492
516
  if (blockA.type === "code" || blockA.type === "equation")
@@ -540,18 +564,6 @@ function stripPrefix(block, prefix) {
540
564
  if (stripped.length === 0) return [];
541
565
  return [{ ...first, text: stripped }, ...block.content.slice(1)];
542
566
  }
543
- function buildMeta(type) {
544
- switch (type) {
545
- case "bullet":
546
- case "number":
547
- case "todo":
548
- return { depth: 0 };
549
- case "heading1":
550
- case "heading2":
551
- case "heading3":
552
- return {};
553
- }
554
- }
555
567
  function applyMarkdownTransform(block, cursorOffset) {
556
568
  if (block.type !== "paragraph")
557
569
  return import_result4.Result.Ok({ block, converted: false });
@@ -568,7 +580,7 @@ function applyMarkdownTransform(block, cursorOffset) {
568
580
  id: block.id,
569
581
  type: targetType,
570
582
  content: strippedContent,
571
- meta: buildMeta(targetType)
583
+ meta: buildMetaForTarget(targetType)
572
584
  };
573
585
  return import_result4.Result.Ok({ block: converted, converted: true });
574
586
  }
@@ -795,67 +807,8 @@ ${block.content[0].text}
795
807
  }).join("\n\n");
796
808
  }
797
809
 
798
- // src/engine/cursor.ts
799
- var import_result6 = require("@reiwuzen/result");
800
- function getTextLength2(node) {
801
- if (node.type === "text" || node.type === "code") return node.text.length;
802
- if (node.type === "equation") return node.latex.length;
803
- return 0;
804
- }
805
- function flatToPosition(block, flatOffset) {
806
- if (block.type === "code" || block.type === "equation") {
807
- const node = block.content[0];
808
- const len = getTextLength2(node);
809
- if (flatOffset < 0 || flatOffset > len)
810
- return import_result6.Result.Err(`flatOffset (${flatOffset}) out of bounds (length=${len})`);
811
- return import_result6.Result.Ok({ nodeIndex: 0, offset: flatOffset });
812
- }
813
- const nodes = block.content;
814
- if (flatOffset < 0)
815
- return import_result6.Result.Err(`flatOffset (${flatOffset}) cannot be negative`);
816
- let accumulated = 0;
817
- for (let i = 0; i < nodes.length; i++) {
818
- const len = getTextLength2(nodes[i]);
819
- if (flatOffset <= accumulated + len) {
820
- return import_result6.Result.Ok({ nodeIndex: i, offset: flatOffset - accumulated });
821
- }
822
- accumulated += len;
823
- }
824
- if (flatOffset === accumulated) {
825
- const last = nodes.length - 1;
826
- return import_result6.Result.Ok({ nodeIndex: Math.max(0, last), offset: getTextLength2(nodes[last]) });
827
- }
828
- return import_result6.Result.Err(
829
- `flatOffset (${flatOffset}) out of bounds (total length=${accumulated})`
830
- );
831
- }
832
- function flatToSelection(block, start, end) {
833
- if (start > end)
834
- return import_result6.Result.Err(`start (${start}) cannot be greater than end (${end})`);
835
- return flatToPosition(block, start).andThen(
836
- (startPos) => flatToPosition(block, end).map((endPos) => ({
837
- startIndex: startPos.nodeIndex,
838
- startOffset: startPos.offset,
839
- endIndex: endPos.nodeIndex,
840
- endOffset: endPos.offset
841
- }))
842
- );
843
- }
844
- function positionToFlat(block, nodeIndex, offset) {
845
- if (block.type === "code" || block.type === "equation")
846
- return import_result6.Result.Ok(offset);
847
- const nodes = block.content;
848
- if (nodeIndex < 0 || nodeIndex >= nodes.length)
849
- return import_result6.Result.Err(`nodeIndex (${nodeIndex}) out of bounds`);
850
- let flat = 0;
851
- for (let i = 0; i < nodeIndex; i++) {
852
- flat += getTextLength2(nodes[i]);
853
- }
854
- return import_result6.Result.Ok(flat + offset);
855
- }
856
-
857
810
  // src/engine/history.ts
858
- var import_result7 = require("@reiwuzen/result");
811
+ var import_result6 = require("@reiwuzen/result");
859
812
  function createHistory(initialBlocks) {
860
813
  return {
861
814
  past: [],
@@ -873,9 +826,9 @@ function push(history, blocks, maxSize = 100) {
873
826
  }
874
827
  function undo(history) {
875
828
  if (history.past.length === 0)
876
- return import_result7.Result.Err("Nothing to undo");
829
+ return import_result6.Result.Err("Nothing to undo");
877
830
  const previous = history.past[history.past.length - 1];
878
- return import_result7.Result.Ok({
831
+ return import_result6.Result.Ok({
879
832
  past: history.past.slice(0, -1),
880
833
  present: previous,
881
834
  future: [history.present, ...history.future]
@@ -883,9 +836,9 @@ function undo(history) {
883
836
  }
884
837
  function redo(history) {
885
838
  if (history.future.length === 0)
886
- return import_result7.Result.Err("Nothing to redo");
839
+ return import_result6.Result.Err("Nothing to redo");
887
840
  const next = history.future[0];
888
- return import_result7.Result.Ok({
841
+ return import_result6.Result.Ok({
889
842
  past: [...history.past, history.present],
890
843
  present: next,
891
844
  future: history.future.slice(1)
@@ -894,50 +847,9 @@ function redo(history) {
894
847
  var canUndo = (history) => history.past.length > 0;
895
848
  var canRedo = (history) => history.future.length > 0;
896
849
  var currentBlocks = (history) => history.present.blocks;
897
-
898
- // src/engine/block.ts
899
- function blockInsertAt(block, nodeIndex, offset, incoming) {
900
- return insertAt(block, nodeIndex, offset, incoming).map(
901
- (content) => ({
902
- ...block,
903
- content
904
- })
905
- );
906
- }
907
- function blockDeleteLastChar(block) {
908
- return deleteLastChar(block).map(
909
- (content) => ({
910
- ...block,
911
- content
912
- })
913
- );
914
- }
915
- function blockDeleteRange(block, startNodeIndex, startOffset, endNodeIndex, endOffset) {
916
- return deleteRange(
917
- block,
918
- startNodeIndex,
919
- startOffset,
920
- endNodeIndex,
921
- endOffset
922
- ).map((content) => ({ ...block, content }));
923
- }
924
- function blockReplaceRange(block, startNodeIndex, startOffset, endNodeIndex, endOffset, incoming) {
925
- return replaceRange(
926
- block,
927
- startNodeIndex,
928
- startOffset,
929
- endNodeIndex,
930
- endOffset,
931
- incoming
932
- ).map((content) => ({ ...block, content }));
933
- }
934
850
  // Annotate the CommonJS export names for ESM import in node:
935
851
  0 && (module.exports = {
936
852
  applyMarkdownTransform,
937
- blockDeleteLastChar,
938
- blockDeleteRange,
939
- blockInsertAt,
940
- blockReplaceRange,
941
853
  canRedo,
942
854
  canUndo,
943
855
  changeBlockType,
@@ -952,8 +864,6 @@ function blockReplaceRange(block, startNodeIndex, startOffset, endNodeIndex, end
952
864
  deserializeNodes,
953
865
  duplicateBlock,
954
866
  duplicateBlockAfter,
955
- flatToPosition,
956
- flatToSelection,
957
867
  formatNodes,
958
868
  generateId,
959
869
  indentBlock,
@@ -963,7 +873,6 @@ function blockReplaceRange(block, startNodeIndex, startOffset, endNodeIndex, end
963
873
  mergeBlocks,
964
874
  moveBlock,
965
875
  outdentBlock,
966
- positionToFlat,
967
876
  push,
968
877
  redo,
969
878
  removeLink,
package/dist/index.d.cts CHANGED
@@ -197,41 +197,6 @@ declare function toPlainText(nodes: Node[]): string;
197
197
  */
198
198
  declare function toMarkdown(blocks: AnyBlock[]): string;
199
199
 
200
- type CursorPosition = {
201
- nodeIndex: number;
202
- offset: number;
203
- };
204
- /**
205
- * Convert a flat UI cursor offset to { nodeIndex, offset }.
206
- *
207
- * The browser gives a single number representing position in the
208
- * concatenated text of the block. This function walks the node array
209
- * and finds which node that position falls in and where within it.
210
- *
211
- * e.g. nodes = ["Hello"(5), " World"(6)]
212
- * flatOffset=0 → { nodeIndex: 0, offset: 0 }
213
- * flatOffset=5 → { nodeIndex: 0, offset: 5 } (end of first node)
214
- * flatOffset=6 → { nodeIndex: 1, offset: 1 }
215
- * flatOffset=11 → { nodeIndex: 1, offset: 6 } (end of last node)
216
- */
217
- declare function flatToPosition(block: AnyBlock, flatOffset: number): Result<CursorPosition, string>;
218
- /**
219
- * Convert a flat UI selection { start, end } to NodeSelection.
220
- *
221
- * The browser gives two flat offsets from window.getSelection().
222
- * This calls flatToPosition twice and returns a NodeSelection
223
- * ready to pass to toggleBold, formatNodes, deleteRange, etc.
224
- *
225
- * e.g. nodes = ["Hello"(bold), " World"]
226
- * { start: 3, end: 8 } → { startIndex:0, startOffset:3, endIndex:1, endOffset:3 }
227
- */
228
- declare function flatToSelection(block: AnyBlock, start: number, end: number): Result<NodeSelection, string>;
229
- /**
230
- * Inverse — convert { nodeIndex, offset } back to a flat offset.
231
- * Useful after engine operations to restore cursor position in the DOM.
232
- */
233
- declare function positionToFlat(block: AnyBlock, nodeIndex: number, offset: number): Result<number, string>;
234
-
235
200
  type HistoryEntry = {
236
201
  blocks: AnyBlock[];
237
202
  timestamp: number;
@@ -277,7 +242,7 @@ declare function generateId(fn?: () => string): string;
277
242
  * @param type - the block type to create
278
243
  * @param idFn - optional custom id generator (defaults to uuid v7)
279
244
  */
280
- declare function createBlock<T extends BlockType>(type: T, idFn?: () => string): Result<Block<T>, unknown>;
245
+ declare function createBlock<T extends BlockType>(type: T, idFn?: () => string): Result<AnyBlock, unknown>;
281
246
  declare function createBlockAfter<T extends BlockType>(blocks: AnyBlock[], afterId: string, type: T, idFn?: () => string): Result<{
282
247
  blocks: AnyBlock[];
283
248
  newId: string;
@@ -302,13 +267,4 @@ declare function duplicateBlockAfter(blocks: AnyBlock[], id: string, newId?: str
302
267
  */
303
268
  declare function moveBlock(blocks: AnyBlock[], id: string, direction: "up" | "down"): Result<AnyBlock[], string>;
304
269
 
305
- /**─── Wrappers ──────────────────────────────────────────────────────────────────
306
- * Each function runs the content engine operation and returns Result<AnyBlock>
307
- instead of Result<BlockContent<T>> — caller gets the full updated block back.
308
- */
309
- declare function blockInsertAt(block: AnyBlock, nodeIndex: number, offset: number, incoming: Node): Result<AnyBlock>;
310
- declare function blockDeleteLastChar(block: AnyBlock): Result<AnyBlock>;
311
- declare function blockDeleteRange(block: AnyBlock, startNodeIndex: number, startOffset: number, endNodeIndex: number, endOffset: number): Result<AnyBlock>;
312
- declare function blockReplaceRange(block: AnyBlock, startNodeIndex: number, startOffset: number, endNodeIndex: number, endOffset: number, incoming: Node): Result<AnyBlock>;
313
-
314
- export { type AnyBlock, type Block, type BlockContent, type BlockMeta, type BlockType, type History, type HistoryEntry, type Node, type NodeSelection, applyMarkdownTransform, blockDeleteLastChar, blockDeleteRange, blockInsertAt, blockReplaceRange, canRedo, canUndo, changeBlockType, createBlock, createBlockAfter, createHistory, currentBlocks, deleteBlock, deleteLastChar, deleteRange, deserialize, deserializeNodes, duplicateBlock, duplicateBlockAfter, flatToPosition, flatToSelection, formatNodes, generateId, indentBlock, insertAt, insertBlockAfter, mergeAdjacentNodes, mergeBlocks, moveBlock, outdentBlock, positionToFlat, push, redo, removeLink, replaceRange, serialize, serializeNodes, setLink, splitBlock, toMarkdown, toPlainText, toggleBold, toggleColor, toggleHighlight, toggleItalic, toggleStrikethrough, toggleTodo, toggleUnderline, undo };
270
+ export { type AnyBlock, type Block, type BlockContent, type BlockMeta, type BlockType, type History, type HistoryEntry, type Node, type NodeSelection, applyMarkdownTransform, canRedo, canUndo, changeBlockType, createBlock, createBlockAfter, createHistory, currentBlocks, deleteBlock, deleteLastChar, deleteRange, deserialize, deserializeNodes, duplicateBlock, duplicateBlockAfter, formatNodes, generateId, indentBlock, insertAt, insertBlockAfter, mergeAdjacentNodes, mergeBlocks, moveBlock, outdentBlock, push, redo, removeLink, replaceRange, serialize, serializeNodes, setLink, splitBlock, toMarkdown, toPlainText, toggleBold, toggleColor, toggleHighlight, toggleItalic, toggleStrikethrough, toggleTodo, toggleUnderline, undo };
package/dist/index.d.ts CHANGED
@@ -197,41 +197,6 @@ declare function toPlainText(nodes: Node[]): string;
197
197
  */
198
198
  declare function toMarkdown(blocks: AnyBlock[]): string;
199
199
 
200
- type CursorPosition = {
201
- nodeIndex: number;
202
- offset: number;
203
- };
204
- /**
205
- * Convert a flat UI cursor offset to { nodeIndex, offset }.
206
- *
207
- * The browser gives a single number representing position in the
208
- * concatenated text of the block. This function walks the node array
209
- * and finds which node that position falls in and where within it.
210
- *
211
- * e.g. nodes = ["Hello"(5), " World"(6)]
212
- * flatOffset=0 → { nodeIndex: 0, offset: 0 }
213
- * flatOffset=5 → { nodeIndex: 0, offset: 5 } (end of first node)
214
- * flatOffset=6 → { nodeIndex: 1, offset: 1 }
215
- * flatOffset=11 → { nodeIndex: 1, offset: 6 } (end of last node)
216
- */
217
- declare function flatToPosition(block: AnyBlock, flatOffset: number): Result<CursorPosition, string>;
218
- /**
219
- * Convert a flat UI selection { start, end } to NodeSelection.
220
- *
221
- * The browser gives two flat offsets from window.getSelection().
222
- * This calls flatToPosition twice and returns a NodeSelection
223
- * ready to pass to toggleBold, formatNodes, deleteRange, etc.
224
- *
225
- * e.g. nodes = ["Hello"(bold), " World"]
226
- * { start: 3, end: 8 } → { startIndex:0, startOffset:3, endIndex:1, endOffset:3 }
227
- */
228
- declare function flatToSelection(block: AnyBlock, start: number, end: number): Result<NodeSelection, string>;
229
- /**
230
- * Inverse — convert { nodeIndex, offset } back to a flat offset.
231
- * Useful after engine operations to restore cursor position in the DOM.
232
- */
233
- declare function positionToFlat(block: AnyBlock, nodeIndex: number, offset: number): Result<number, string>;
234
-
235
200
  type HistoryEntry = {
236
201
  blocks: AnyBlock[];
237
202
  timestamp: number;
@@ -277,7 +242,7 @@ declare function generateId(fn?: () => string): string;
277
242
  * @param type - the block type to create
278
243
  * @param idFn - optional custom id generator (defaults to uuid v7)
279
244
  */
280
- declare function createBlock<T extends BlockType>(type: T, idFn?: () => string): Result<Block<T>, unknown>;
245
+ declare function createBlock<T extends BlockType>(type: T, idFn?: () => string): Result<AnyBlock, unknown>;
281
246
  declare function createBlockAfter<T extends BlockType>(blocks: AnyBlock[], afterId: string, type: T, idFn?: () => string): Result<{
282
247
  blocks: AnyBlock[];
283
248
  newId: string;
@@ -302,13 +267,4 @@ declare function duplicateBlockAfter(blocks: AnyBlock[], id: string, newId?: str
302
267
  */
303
268
  declare function moveBlock(blocks: AnyBlock[], id: string, direction: "up" | "down"): Result<AnyBlock[], string>;
304
269
 
305
- /**─── Wrappers ──────────────────────────────────────────────────────────────────
306
- * Each function runs the content engine operation and returns Result<AnyBlock>
307
- instead of Result<BlockContent<T>> — caller gets the full updated block back.
308
- */
309
- declare function blockInsertAt(block: AnyBlock, nodeIndex: number, offset: number, incoming: Node): Result<AnyBlock>;
310
- declare function blockDeleteLastChar(block: AnyBlock): Result<AnyBlock>;
311
- declare function blockDeleteRange(block: AnyBlock, startNodeIndex: number, startOffset: number, endNodeIndex: number, endOffset: number): Result<AnyBlock>;
312
- declare function blockReplaceRange(block: AnyBlock, startNodeIndex: number, startOffset: number, endNodeIndex: number, endOffset: number, incoming: Node): Result<AnyBlock>;
313
-
314
- export { type AnyBlock, type Block, type BlockContent, type BlockMeta, type BlockType, type History, type HistoryEntry, type Node, type NodeSelection, applyMarkdownTransform, blockDeleteLastChar, blockDeleteRange, blockInsertAt, blockReplaceRange, canRedo, canUndo, changeBlockType, createBlock, createBlockAfter, createHistory, currentBlocks, deleteBlock, deleteLastChar, deleteRange, deserialize, deserializeNodes, duplicateBlock, duplicateBlockAfter, flatToPosition, flatToSelection, formatNodes, generateId, indentBlock, insertAt, insertBlockAfter, mergeAdjacentNodes, mergeBlocks, moveBlock, outdentBlock, positionToFlat, push, redo, removeLink, replaceRange, serialize, serializeNodes, setLink, splitBlock, toMarkdown, toPlainText, toggleBold, toggleColor, toggleHighlight, toggleItalic, toggleStrikethrough, toggleTodo, toggleUnderline, undo };
270
+ export { type AnyBlock, type Block, type BlockContent, type BlockMeta, type BlockType, type History, type HistoryEntry, type Node, type NodeSelection, applyMarkdownTransform, canRedo, canUndo, changeBlockType, createBlock, createBlockAfter, createHistory, currentBlocks, deleteBlock, deleteLastChar, deleteRange, deserialize, deserializeNodes, duplicateBlock, duplicateBlockAfter, formatNodes, generateId, indentBlock, insertAt, insertBlockAfter, mergeAdjacentNodes, mergeBlocks, moveBlock, outdentBlock, push, redo, removeLink, replaceRange, serialize, serializeNodes, setLink, splitBlock, toMarkdown, toPlainText, toggleBold, toggleColor, toggleHighlight, toggleItalic, toggleStrikethrough, toggleTodo, toggleUnderline, undo };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // src/utils/block.ts
1
+ // src/engine/block.ts
2
2
  import { Result } from "@reiwuzen/result";
3
3
  import { v7 } from "uuid";
4
4
  function generateId(fn) {
@@ -394,25 +394,56 @@ function splitBlock(block, nodeIndex, offset) {
394
394
  const targetLen = target ? getTextLength(target) : 0;
395
395
  if (offset < 0 || offset > targetLen)
396
396
  return Result3.Err(`offset (${offset}) out of bounds`);
397
+ const atStart = nodeIndex === 0 && offset === 0;
398
+ const atEnd = nodeIndex === nodes.length - 1 && offset === targetLen;
397
399
  const beforeNodes = [
398
400
  ...nodes.slice(0, nodeIndex),
399
- ...target && offset > 0 ? [sliceNode(target, 0, offset)].filter(Boolean) : []
401
+ ...target && offset > 0 ? [sliceNode(target, 0, offset)].filter(isNode) : []
400
402
  ];
401
403
  const afterNodes = [
402
- ...target && offset < targetLen ? [sliceNode(target, offset, targetLen)].filter(Boolean) : [],
404
+ ...target && offset < targetLen ? [sliceNode(target, offset, targetLen)].filter(isNode) : [],
403
405
  ...nodes.slice(nodeIndex + 1)
404
406
  ];
405
- const original = {
407
+ if (atStart) {
408
+ const left2 = {
409
+ ...block,
410
+ content: []
411
+ };
412
+ const right2 = {
413
+ id: generateId(),
414
+ type: block.type,
415
+ content: nodes,
416
+ meta: {}
417
+ };
418
+ return Result3.Ok([left2, right2]);
419
+ }
420
+ if (atEnd) {
421
+ const left2 = {
422
+ ...block,
423
+ content: nodes
424
+ };
425
+ const right2 = {
426
+ id: generateId(),
427
+ type: "paragraph",
428
+ content: [],
429
+ meta: {}
430
+ };
431
+ return Result3.Ok([left2, right2]);
432
+ }
433
+ const left = {
406
434
  ...block,
407
435
  content: beforeNodes
408
436
  };
409
- const newBlock = {
437
+ const right = {
410
438
  id: generateId(),
411
439
  type: "paragraph",
412
440
  content: afterNodes,
413
441
  meta: {}
414
442
  };
415
- return Result3.Ok([original, newBlock]);
443
+ return Result3.Ok([left, right]);
444
+ }
445
+ function isNode(node) {
446
+ return node !== null;
416
447
  }
417
448
  function mergeBlocks(blockA, blockB) {
418
449
  if (blockA.type === "code" || blockA.type === "equation")
@@ -466,18 +497,6 @@ function stripPrefix(block, prefix) {
466
497
  if (stripped.length === 0) return [];
467
498
  return [{ ...first, text: stripped }, ...block.content.slice(1)];
468
499
  }
469
- function buildMeta(type) {
470
- switch (type) {
471
- case "bullet":
472
- case "number":
473
- case "todo":
474
- return { depth: 0 };
475
- case "heading1":
476
- case "heading2":
477
- case "heading3":
478
- return {};
479
- }
480
- }
481
500
  function applyMarkdownTransform(block, cursorOffset) {
482
501
  if (block.type !== "paragraph")
483
502
  return Result4.Ok({ block, converted: false });
@@ -494,7 +513,7 @@ function applyMarkdownTransform(block, cursorOffset) {
494
513
  id: block.id,
495
514
  type: targetType,
496
515
  content: strippedContent,
497
- meta: buildMeta(targetType)
516
+ meta: buildMetaForTarget(targetType)
498
517
  };
499
518
  return Result4.Ok({ block: converted, converted: true });
500
519
  }
@@ -721,67 +740,8 @@ ${block.content[0].text}
721
740
  }).join("\n\n");
722
741
  }
723
742
 
724
- // src/engine/cursor.ts
725
- import { Result as Result6 } from "@reiwuzen/result";
726
- function getTextLength2(node) {
727
- if (node.type === "text" || node.type === "code") return node.text.length;
728
- if (node.type === "equation") return node.latex.length;
729
- return 0;
730
- }
731
- function flatToPosition(block, flatOffset) {
732
- if (block.type === "code" || block.type === "equation") {
733
- const node = block.content[0];
734
- const len = getTextLength2(node);
735
- if (flatOffset < 0 || flatOffset > len)
736
- return Result6.Err(`flatOffset (${flatOffset}) out of bounds (length=${len})`);
737
- return Result6.Ok({ nodeIndex: 0, offset: flatOffset });
738
- }
739
- const nodes = block.content;
740
- if (flatOffset < 0)
741
- return Result6.Err(`flatOffset (${flatOffset}) cannot be negative`);
742
- let accumulated = 0;
743
- for (let i = 0; i < nodes.length; i++) {
744
- const len = getTextLength2(nodes[i]);
745
- if (flatOffset <= accumulated + len) {
746
- return Result6.Ok({ nodeIndex: i, offset: flatOffset - accumulated });
747
- }
748
- accumulated += len;
749
- }
750
- if (flatOffset === accumulated) {
751
- const last = nodes.length - 1;
752
- return Result6.Ok({ nodeIndex: Math.max(0, last), offset: getTextLength2(nodes[last]) });
753
- }
754
- return Result6.Err(
755
- `flatOffset (${flatOffset}) out of bounds (total length=${accumulated})`
756
- );
757
- }
758
- function flatToSelection(block, start, end) {
759
- if (start > end)
760
- return Result6.Err(`start (${start}) cannot be greater than end (${end})`);
761
- return flatToPosition(block, start).andThen(
762
- (startPos) => flatToPosition(block, end).map((endPos) => ({
763
- startIndex: startPos.nodeIndex,
764
- startOffset: startPos.offset,
765
- endIndex: endPos.nodeIndex,
766
- endOffset: endPos.offset
767
- }))
768
- );
769
- }
770
- function positionToFlat(block, nodeIndex, offset) {
771
- if (block.type === "code" || block.type === "equation")
772
- return Result6.Ok(offset);
773
- const nodes = block.content;
774
- if (nodeIndex < 0 || nodeIndex >= nodes.length)
775
- return Result6.Err(`nodeIndex (${nodeIndex}) out of bounds`);
776
- let flat = 0;
777
- for (let i = 0; i < nodeIndex; i++) {
778
- flat += getTextLength2(nodes[i]);
779
- }
780
- return Result6.Ok(flat + offset);
781
- }
782
-
783
743
  // src/engine/history.ts
784
- import { Result as Result7 } from "@reiwuzen/result";
744
+ import { Result as Result6 } from "@reiwuzen/result";
785
745
  function createHistory(initialBlocks) {
786
746
  return {
787
747
  past: [],
@@ -799,9 +759,9 @@ function push(history, blocks, maxSize = 100) {
799
759
  }
800
760
  function undo(history) {
801
761
  if (history.past.length === 0)
802
- return Result7.Err("Nothing to undo");
762
+ return Result6.Err("Nothing to undo");
803
763
  const previous = history.past[history.past.length - 1];
804
- return Result7.Ok({
764
+ return Result6.Ok({
805
765
  past: history.past.slice(0, -1),
806
766
  present: previous,
807
767
  future: [history.present, ...history.future]
@@ -809,9 +769,9 @@ function undo(history) {
809
769
  }
810
770
  function redo(history) {
811
771
  if (history.future.length === 0)
812
- return Result7.Err("Nothing to redo");
772
+ return Result6.Err("Nothing to redo");
813
773
  const next = history.future[0];
814
- return Result7.Ok({
774
+ return Result6.Ok({
815
775
  past: [...history.past, history.present],
816
776
  present: next,
817
777
  future: history.future.slice(1)
@@ -820,49 +780,8 @@ function redo(history) {
820
780
  var canUndo = (history) => history.past.length > 0;
821
781
  var canRedo = (history) => history.future.length > 0;
822
782
  var currentBlocks = (history) => history.present.blocks;
823
-
824
- // src/engine/block.ts
825
- function blockInsertAt(block, nodeIndex, offset, incoming) {
826
- return insertAt(block, nodeIndex, offset, incoming).map(
827
- (content) => ({
828
- ...block,
829
- content
830
- })
831
- );
832
- }
833
- function blockDeleteLastChar(block) {
834
- return deleteLastChar(block).map(
835
- (content) => ({
836
- ...block,
837
- content
838
- })
839
- );
840
- }
841
- function blockDeleteRange(block, startNodeIndex, startOffset, endNodeIndex, endOffset) {
842
- return deleteRange(
843
- block,
844
- startNodeIndex,
845
- startOffset,
846
- endNodeIndex,
847
- endOffset
848
- ).map((content) => ({ ...block, content }));
849
- }
850
- function blockReplaceRange(block, startNodeIndex, startOffset, endNodeIndex, endOffset, incoming) {
851
- return replaceRange(
852
- block,
853
- startNodeIndex,
854
- startOffset,
855
- endNodeIndex,
856
- endOffset,
857
- incoming
858
- ).map((content) => ({ ...block, content }));
859
- }
860
783
  export {
861
784
  applyMarkdownTransform,
862
- blockDeleteLastChar,
863
- blockDeleteRange,
864
- blockInsertAt,
865
- blockReplaceRange,
866
785
  canRedo,
867
786
  canUndo,
868
787
  changeBlockType,
@@ -877,8 +796,6 @@ export {
877
796
  deserializeNodes,
878
797
  duplicateBlock,
879
798
  duplicateBlockAfter,
880
- flatToPosition,
881
- flatToSelection,
882
799
  formatNodes,
883
800
  generateId,
884
801
  indentBlock,
@@ -888,7 +805,6 @@ export {
888
805
  mergeBlocks,
889
806
  moveBlock,
890
807
  outdentBlock,
891
- positionToFlat,
892
808
  push,
893
809
  redo,
894
810
  removeLink,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reiwuzen/blocky",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Pure TypeScript block editor engine. Headless, framework-agnostic — content mutation, formatting, transforms, serialization, and history.",
5
5
  "author": "Rei WuZen",
6
6
  "license": "ISC",