@reiwuzen/blocky 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +79 -139
- package/dist/index.d.cts +11 -47
- package/dist/index.d.ts +11 -47
- package/dist/index.js +77 -132
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -21,14 +21,11 @@ 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,
|
|
31
27
|
createBlock: () => createBlock,
|
|
28
|
+
createBlockAfter: () => createBlockAfter,
|
|
32
29
|
createHistory: () => createHistory,
|
|
33
30
|
currentBlocks: () => currentBlocks,
|
|
34
31
|
deleteBlock: () => deleteBlock,
|
|
@@ -37,8 +34,7 @@ __export(index_exports, {
|
|
|
37
34
|
deserialize: () => deserialize,
|
|
38
35
|
deserializeNodes: () => deserializeNodes,
|
|
39
36
|
duplicateBlock: () => duplicateBlock,
|
|
40
|
-
|
|
41
|
-
flatToSelection: () => flatToSelection,
|
|
37
|
+
duplicateBlockAfter: () => duplicateBlockAfter,
|
|
42
38
|
formatNodes: () => formatNodes,
|
|
43
39
|
generateId: () => generateId,
|
|
44
40
|
indentBlock: () => indentBlock,
|
|
@@ -48,7 +44,6 @@ __export(index_exports, {
|
|
|
48
44
|
mergeBlocks: () => mergeBlocks,
|
|
49
45
|
moveBlock: () => moveBlock,
|
|
50
46
|
outdentBlock: () => outdentBlock,
|
|
51
|
-
positionToFlat: () => positionToFlat,
|
|
52
47
|
push: () => push,
|
|
53
48
|
redo: () => redo,
|
|
54
49
|
removeLink: () => removeLink,
|
|
@@ -70,7 +65,7 @@ __export(index_exports, {
|
|
|
70
65
|
});
|
|
71
66
|
module.exports = __toCommonJS(index_exports);
|
|
72
67
|
|
|
73
|
-
// src/
|
|
68
|
+
// src/engine/block.ts
|
|
74
69
|
var import_result = require("@reiwuzen/result");
|
|
75
70
|
var import_uuid = require("uuid");
|
|
76
71
|
function generateId(fn) {
|
|
@@ -109,12 +104,28 @@ function createBlock(type, idFn) {
|
|
|
109
104
|
return block;
|
|
110
105
|
});
|
|
111
106
|
}
|
|
112
|
-
function
|
|
113
|
-
return createBlock(type, idFn).
|
|
107
|
+
function createBlockAfter(blocks, afterId, type, idFn) {
|
|
108
|
+
return createBlock(type, idFn).andThen((newBlock) => {
|
|
114
109
|
const index = blocks.findIndex((b) => b.id === afterId);
|
|
110
|
+
if (index === -1) return import_result.Result.Err(`[BlockNotFound]: ${afterId}`);
|
|
115
111
|
const next = [...blocks];
|
|
116
112
|
next.splice(index + 1, 0, newBlock);
|
|
117
|
-
return { blocks: next, newId: newBlock.id };
|
|
113
|
+
return import_result.Result.Ok({ blocks: next, newId: newBlock.id });
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function insertBlockAfter(blocks, afterId, insertBlock) {
|
|
117
|
+
const targetBlockIndex = blocks.findIndex((b) => b.id === afterId);
|
|
118
|
+
if (targetBlockIndex === -1) {
|
|
119
|
+
return import_result.Result.Err(`No block found with id: ${afterId}`);
|
|
120
|
+
}
|
|
121
|
+
const newBlocks = [
|
|
122
|
+
...blocks.slice(0, targetBlockIndex + 1),
|
|
123
|
+
insertBlock,
|
|
124
|
+
...blocks.slice(targetBlockIndex + 1)
|
|
125
|
+
];
|
|
126
|
+
return import_result.Result.Ok({
|
|
127
|
+
blocks: newBlocks,
|
|
128
|
+
newFocusId: insertBlock.id
|
|
118
129
|
});
|
|
119
130
|
}
|
|
120
131
|
function deleteBlock(blocks, id) {
|
|
@@ -122,8 +133,19 @@ function deleteBlock(blocks, id) {
|
|
|
122
133
|
const prevId = blocks[index - 1]?.id ?? blocks[index + 1]?.id ?? "";
|
|
123
134
|
return { blocks: blocks.filter((b) => b.id !== id), prevId };
|
|
124
135
|
}
|
|
125
|
-
function duplicateBlock(incoming, newId) {
|
|
126
|
-
return {
|
|
136
|
+
function duplicateBlock(incoming, newId = crypto.randomUUID()) {
|
|
137
|
+
return {
|
|
138
|
+
...incoming,
|
|
139
|
+
id: newId,
|
|
140
|
+
meta: { ...incoming.meta },
|
|
141
|
+
content: incoming.content.map((node) => ({ ...node }))
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function duplicateBlockAfter(blocks, id, newId) {
|
|
145
|
+
const targetBlock = blocks.find((b) => b.id === id);
|
|
146
|
+
if (targetBlock == null) return import_result.Result.Err(`[BlockNotFound]: ${id}`);
|
|
147
|
+
const dup = duplicateBlock(targetBlock, newId);
|
|
148
|
+
return insertBlockAfter(blocks, id, dup);
|
|
127
149
|
}
|
|
128
150
|
function moveBlock(blocks, id, direction) {
|
|
129
151
|
const index = blocks.findIndex((b) => b.id === id);
|
|
@@ -439,25 +461,56 @@ function splitBlock(block, nodeIndex, offset) {
|
|
|
439
461
|
const targetLen = target ? getTextLength(target) : 0;
|
|
440
462
|
if (offset < 0 || offset > targetLen)
|
|
441
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;
|
|
442
466
|
const beforeNodes = [
|
|
443
467
|
...nodes.slice(0, nodeIndex),
|
|
444
|
-
...target && offset > 0 ? [sliceNode(target, 0, offset)].filter(
|
|
468
|
+
...target && offset > 0 ? [sliceNode(target, 0, offset)].filter(isNode) : []
|
|
445
469
|
];
|
|
446
470
|
const afterNodes = [
|
|
447
|
-
...target && offset < targetLen ? [sliceNode(target, offset, targetLen)].filter(
|
|
471
|
+
...target && offset < targetLen ? [sliceNode(target, offset, targetLen)].filter(isNode) : [],
|
|
448
472
|
...nodes.slice(nodeIndex + 1)
|
|
449
473
|
];
|
|
450
|
-
|
|
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 = {
|
|
451
501
|
...block,
|
|
452
502
|
content: beforeNodes
|
|
453
503
|
};
|
|
454
|
-
const
|
|
504
|
+
const right = {
|
|
455
505
|
id: generateId(),
|
|
456
506
|
type: "paragraph",
|
|
457
507
|
content: afterNodes,
|
|
458
508
|
meta: {}
|
|
459
509
|
};
|
|
460
|
-
return import_result3.Result.Ok([
|
|
510
|
+
return import_result3.Result.Ok([left, right]);
|
|
511
|
+
}
|
|
512
|
+
function isNode(node) {
|
|
513
|
+
return node !== null;
|
|
461
514
|
}
|
|
462
515
|
function mergeBlocks(blockA, blockB) {
|
|
463
516
|
if (blockA.type === "code" || blockA.type === "equation")
|
|
@@ -511,18 +564,6 @@ function stripPrefix(block, prefix) {
|
|
|
511
564
|
if (stripped.length === 0) return [];
|
|
512
565
|
return [{ ...first, text: stripped }, ...block.content.slice(1)];
|
|
513
566
|
}
|
|
514
|
-
function buildMeta(type) {
|
|
515
|
-
switch (type) {
|
|
516
|
-
case "bullet":
|
|
517
|
-
case "number":
|
|
518
|
-
case "todo":
|
|
519
|
-
return { depth: 0 };
|
|
520
|
-
case "heading1":
|
|
521
|
-
case "heading2":
|
|
522
|
-
case "heading3":
|
|
523
|
-
return {};
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
567
|
function applyMarkdownTransform(block, cursorOffset) {
|
|
527
568
|
if (block.type !== "paragraph")
|
|
528
569
|
return import_result4.Result.Ok({ block, converted: false });
|
|
@@ -539,7 +580,7 @@ function applyMarkdownTransform(block, cursorOffset) {
|
|
|
539
580
|
id: block.id,
|
|
540
581
|
type: targetType,
|
|
541
582
|
content: strippedContent,
|
|
542
|
-
meta:
|
|
583
|
+
meta: buildMetaForTarget(targetType)
|
|
543
584
|
};
|
|
544
585
|
return import_result4.Result.Ok({ block: converted, converted: true });
|
|
545
586
|
}
|
|
@@ -766,67 +807,8 @@ ${block.content[0].text}
|
|
|
766
807
|
}).join("\n\n");
|
|
767
808
|
}
|
|
768
809
|
|
|
769
|
-
// src/engine/cursor.ts
|
|
770
|
-
var import_result6 = require("@reiwuzen/result");
|
|
771
|
-
function getTextLength2(node) {
|
|
772
|
-
if (node.type === "text" || node.type === "code") return node.text.length;
|
|
773
|
-
if (node.type === "equation") return node.latex.length;
|
|
774
|
-
return 0;
|
|
775
|
-
}
|
|
776
|
-
function flatToPosition(block, flatOffset) {
|
|
777
|
-
if (block.type === "code" || block.type === "equation") {
|
|
778
|
-
const node = block.content[0];
|
|
779
|
-
const len = getTextLength2(node);
|
|
780
|
-
if (flatOffset < 0 || flatOffset > len)
|
|
781
|
-
return import_result6.Result.Err(`flatOffset (${flatOffset}) out of bounds (length=${len})`);
|
|
782
|
-
return import_result6.Result.Ok({ nodeIndex: 0, offset: flatOffset });
|
|
783
|
-
}
|
|
784
|
-
const nodes = block.content;
|
|
785
|
-
if (flatOffset < 0)
|
|
786
|
-
return import_result6.Result.Err(`flatOffset (${flatOffset}) cannot be negative`);
|
|
787
|
-
let accumulated = 0;
|
|
788
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
789
|
-
const len = getTextLength2(nodes[i]);
|
|
790
|
-
if (flatOffset <= accumulated + len) {
|
|
791
|
-
return import_result6.Result.Ok({ nodeIndex: i, offset: flatOffset - accumulated });
|
|
792
|
-
}
|
|
793
|
-
accumulated += len;
|
|
794
|
-
}
|
|
795
|
-
if (flatOffset === accumulated) {
|
|
796
|
-
const last = nodes.length - 1;
|
|
797
|
-
return import_result6.Result.Ok({ nodeIndex: Math.max(0, last), offset: getTextLength2(nodes[last]) });
|
|
798
|
-
}
|
|
799
|
-
return import_result6.Result.Err(
|
|
800
|
-
`flatOffset (${flatOffset}) out of bounds (total length=${accumulated})`
|
|
801
|
-
);
|
|
802
|
-
}
|
|
803
|
-
function flatToSelection(block, start, end) {
|
|
804
|
-
if (start > end)
|
|
805
|
-
return import_result6.Result.Err(`start (${start}) cannot be greater than end (${end})`);
|
|
806
|
-
return flatToPosition(block, start).andThen(
|
|
807
|
-
(startPos) => flatToPosition(block, end).map((endPos) => ({
|
|
808
|
-
startIndex: startPos.nodeIndex,
|
|
809
|
-
startOffset: startPos.offset,
|
|
810
|
-
endIndex: endPos.nodeIndex,
|
|
811
|
-
endOffset: endPos.offset
|
|
812
|
-
}))
|
|
813
|
-
);
|
|
814
|
-
}
|
|
815
|
-
function positionToFlat(block, nodeIndex, offset) {
|
|
816
|
-
if (block.type === "code" || block.type === "equation")
|
|
817
|
-
return import_result6.Result.Ok(offset);
|
|
818
|
-
const nodes = block.content;
|
|
819
|
-
if (nodeIndex < 0 || nodeIndex >= nodes.length)
|
|
820
|
-
return import_result6.Result.Err(`nodeIndex (${nodeIndex}) out of bounds`);
|
|
821
|
-
let flat = 0;
|
|
822
|
-
for (let i = 0; i < nodeIndex; i++) {
|
|
823
|
-
flat += getTextLength2(nodes[i]);
|
|
824
|
-
}
|
|
825
|
-
return import_result6.Result.Ok(flat + offset);
|
|
826
|
-
}
|
|
827
|
-
|
|
828
810
|
// src/engine/history.ts
|
|
829
|
-
var
|
|
811
|
+
var import_result6 = require("@reiwuzen/result");
|
|
830
812
|
function createHistory(initialBlocks) {
|
|
831
813
|
return {
|
|
832
814
|
past: [],
|
|
@@ -844,9 +826,9 @@ function push(history, blocks, maxSize = 100) {
|
|
|
844
826
|
}
|
|
845
827
|
function undo(history) {
|
|
846
828
|
if (history.past.length === 0)
|
|
847
|
-
return
|
|
829
|
+
return import_result6.Result.Err("Nothing to undo");
|
|
848
830
|
const previous = history.past[history.past.length - 1];
|
|
849
|
-
return
|
|
831
|
+
return import_result6.Result.Ok({
|
|
850
832
|
past: history.past.slice(0, -1),
|
|
851
833
|
present: previous,
|
|
852
834
|
future: [history.present, ...history.future]
|
|
@@ -854,9 +836,9 @@ function undo(history) {
|
|
|
854
836
|
}
|
|
855
837
|
function redo(history) {
|
|
856
838
|
if (history.future.length === 0)
|
|
857
|
-
return
|
|
839
|
+
return import_result6.Result.Err("Nothing to redo");
|
|
858
840
|
const next = history.future[0];
|
|
859
|
-
return
|
|
841
|
+
return import_result6.Result.Ok({
|
|
860
842
|
past: [...history.past, history.present],
|
|
861
843
|
present: next,
|
|
862
844
|
future: history.future.slice(1)
|
|
@@ -865,54 +847,14 @@ function redo(history) {
|
|
|
865
847
|
var canUndo = (history) => history.past.length > 0;
|
|
866
848
|
var canRedo = (history) => history.future.length > 0;
|
|
867
849
|
var currentBlocks = (history) => history.present.blocks;
|
|
868
|
-
|
|
869
|
-
// src/engine/block.ts
|
|
870
|
-
function blockInsertAt(block, nodeIndex, offset, incoming) {
|
|
871
|
-
return insertAt(block, nodeIndex, offset, incoming).map(
|
|
872
|
-
(content) => ({
|
|
873
|
-
...block,
|
|
874
|
-
content
|
|
875
|
-
})
|
|
876
|
-
);
|
|
877
|
-
}
|
|
878
|
-
function blockDeleteLastChar(block) {
|
|
879
|
-
return deleteLastChar(block).map(
|
|
880
|
-
(content) => ({
|
|
881
|
-
...block,
|
|
882
|
-
content
|
|
883
|
-
})
|
|
884
|
-
);
|
|
885
|
-
}
|
|
886
|
-
function blockDeleteRange(block, startNodeIndex, startOffset, endNodeIndex, endOffset) {
|
|
887
|
-
return deleteRange(
|
|
888
|
-
block,
|
|
889
|
-
startNodeIndex,
|
|
890
|
-
startOffset,
|
|
891
|
-
endNodeIndex,
|
|
892
|
-
endOffset
|
|
893
|
-
).map((content) => ({ ...block, content }));
|
|
894
|
-
}
|
|
895
|
-
function blockReplaceRange(block, startNodeIndex, startOffset, endNodeIndex, endOffset, incoming) {
|
|
896
|
-
return replaceRange(
|
|
897
|
-
block,
|
|
898
|
-
startNodeIndex,
|
|
899
|
-
startOffset,
|
|
900
|
-
endNodeIndex,
|
|
901
|
-
endOffset,
|
|
902
|
-
incoming
|
|
903
|
-
).map((content) => ({ ...block, content }));
|
|
904
|
-
}
|
|
905
850
|
// Annotate the CommonJS export names for ESM import in node:
|
|
906
851
|
0 && (module.exports = {
|
|
907
852
|
applyMarkdownTransform,
|
|
908
|
-
blockDeleteLastChar,
|
|
909
|
-
blockDeleteRange,
|
|
910
|
-
blockInsertAt,
|
|
911
|
-
blockReplaceRange,
|
|
912
853
|
canRedo,
|
|
913
854
|
canUndo,
|
|
914
855
|
changeBlockType,
|
|
915
856
|
createBlock,
|
|
857
|
+
createBlockAfter,
|
|
916
858
|
createHistory,
|
|
917
859
|
currentBlocks,
|
|
918
860
|
deleteBlock,
|
|
@@ -921,8 +863,7 @@ function blockReplaceRange(block, startNodeIndex, startOffset, endNodeIndex, end
|
|
|
921
863
|
deserialize,
|
|
922
864
|
deserializeNodes,
|
|
923
865
|
duplicateBlock,
|
|
924
|
-
|
|
925
|
-
flatToSelection,
|
|
866
|
+
duplicateBlockAfter,
|
|
926
867
|
formatNodes,
|
|
927
868
|
generateId,
|
|
928
869
|
indentBlock,
|
|
@@ -932,7 +873,6 @@ function blockReplaceRange(block, startNodeIndex, startOffset, endNodeIndex, end
|
|
|
932
873
|
mergeBlocks,
|
|
933
874
|
moveBlock,
|
|
934
875
|
outdentBlock,
|
|
935
|
-
positionToFlat,
|
|
936
876
|
push,
|
|
937
877
|
redo,
|
|
938
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;
|
|
@@ -278,15 +243,23 @@ declare function generateId(fn?: () => string): string;
|
|
|
278
243
|
* @param idFn - optional custom id generator (defaults to uuid v7)
|
|
279
244
|
*/
|
|
280
245
|
declare function createBlock<T extends BlockType>(type: T, idFn?: () => string): Result<Block<T>, unknown>;
|
|
281
|
-
declare function
|
|
246
|
+
declare function createBlockAfter<T extends BlockType>(blocks: AnyBlock[], afterId: string, type: T, idFn?: () => string): Result<{
|
|
282
247
|
blocks: AnyBlock[];
|
|
283
248
|
newId: string;
|
|
284
249
|
}, unknown>;
|
|
250
|
+
declare function insertBlockAfter(blocks: AnyBlock[], afterId: string, insertBlock: AnyBlock): Result<{
|
|
251
|
+
blocks: AnyBlock[];
|
|
252
|
+
newFocusId: string;
|
|
253
|
+
}, unknown>;
|
|
285
254
|
declare function deleteBlock(blocks: AnyBlock[], id: string): {
|
|
286
255
|
blocks: AnyBlock[];
|
|
287
256
|
prevId: string;
|
|
288
257
|
};
|
|
289
|
-
declare function duplicateBlock(incoming: AnyBlock, newId
|
|
258
|
+
declare function duplicateBlock(incoming: AnyBlock, newId?: string): AnyBlock;
|
|
259
|
+
declare function duplicateBlockAfter(blocks: AnyBlock[], id: string, newId?: string): Result<{
|
|
260
|
+
blocks: AnyBlock[];
|
|
261
|
+
newFocusId: string;
|
|
262
|
+
}, unknown>;
|
|
290
263
|
/**
|
|
291
264
|
* Move a block up or down by one position in the array.
|
|
292
265
|
* Returns the same array unchanged if block is already at the boundary.
|
|
@@ -294,13 +267,4 @@ declare function duplicateBlock(incoming: AnyBlock, newId: string): AnyBlock;
|
|
|
294
267
|
*/
|
|
295
268
|
declare function moveBlock(blocks: AnyBlock[], id: string, direction: "up" | "down"): Result<AnyBlock[], string>;
|
|
296
269
|
|
|
297
|
-
|
|
298
|
-
* Each function runs the content engine operation and returns Result<AnyBlock>
|
|
299
|
-
instead of Result<BlockContent<T>> — caller gets the full updated block back.
|
|
300
|
-
*/
|
|
301
|
-
declare function blockInsertAt(block: AnyBlock, nodeIndex: number, offset: number, incoming: Node): Result<AnyBlock>;
|
|
302
|
-
declare function blockDeleteLastChar(block: AnyBlock): Result<AnyBlock>;
|
|
303
|
-
declare function blockDeleteRange(block: AnyBlock, startNodeIndex: number, startOffset: number, endNodeIndex: number, endOffset: number): Result<AnyBlock>;
|
|
304
|
-
declare function blockReplaceRange(block: AnyBlock, startNodeIndex: number, startOffset: number, endNodeIndex: number, endOffset: number, incoming: Node): Result<AnyBlock>;
|
|
305
|
-
|
|
306
|
-
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, createHistory, currentBlocks, deleteBlock, deleteLastChar, deleteRange, deserialize, deserializeNodes, duplicateBlock, 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;
|
|
@@ -278,15 +243,23 @@ declare function generateId(fn?: () => string): string;
|
|
|
278
243
|
* @param idFn - optional custom id generator (defaults to uuid v7)
|
|
279
244
|
*/
|
|
280
245
|
declare function createBlock<T extends BlockType>(type: T, idFn?: () => string): Result<Block<T>, unknown>;
|
|
281
|
-
declare function
|
|
246
|
+
declare function createBlockAfter<T extends BlockType>(blocks: AnyBlock[], afterId: string, type: T, idFn?: () => string): Result<{
|
|
282
247
|
blocks: AnyBlock[];
|
|
283
248
|
newId: string;
|
|
284
249
|
}, unknown>;
|
|
250
|
+
declare function insertBlockAfter(blocks: AnyBlock[], afterId: string, insertBlock: AnyBlock): Result<{
|
|
251
|
+
blocks: AnyBlock[];
|
|
252
|
+
newFocusId: string;
|
|
253
|
+
}, unknown>;
|
|
285
254
|
declare function deleteBlock(blocks: AnyBlock[], id: string): {
|
|
286
255
|
blocks: AnyBlock[];
|
|
287
256
|
prevId: string;
|
|
288
257
|
};
|
|
289
|
-
declare function duplicateBlock(incoming: AnyBlock, newId
|
|
258
|
+
declare function duplicateBlock(incoming: AnyBlock, newId?: string): AnyBlock;
|
|
259
|
+
declare function duplicateBlockAfter(blocks: AnyBlock[], id: string, newId?: string): Result<{
|
|
260
|
+
blocks: AnyBlock[];
|
|
261
|
+
newFocusId: string;
|
|
262
|
+
}, unknown>;
|
|
290
263
|
/**
|
|
291
264
|
* Move a block up or down by one position in the array.
|
|
292
265
|
* Returns the same array unchanged if block is already at the boundary.
|
|
@@ -294,13 +267,4 @@ declare function duplicateBlock(incoming: AnyBlock, newId: string): AnyBlock;
|
|
|
294
267
|
*/
|
|
295
268
|
declare function moveBlock(blocks: AnyBlock[], id: string, direction: "up" | "down"): Result<AnyBlock[], string>;
|
|
296
269
|
|
|
297
|
-
|
|
298
|
-
* Each function runs the content engine operation and returns Result<AnyBlock>
|
|
299
|
-
instead of Result<BlockContent<T>> — caller gets the full updated block back.
|
|
300
|
-
*/
|
|
301
|
-
declare function blockInsertAt(block: AnyBlock, nodeIndex: number, offset: number, incoming: Node): Result<AnyBlock>;
|
|
302
|
-
declare function blockDeleteLastChar(block: AnyBlock): Result<AnyBlock>;
|
|
303
|
-
declare function blockDeleteRange(block: AnyBlock, startNodeIndex: number, startOffset: number, endNodeIndex: number, endOffset: number): Result<AnyBlock>;
|
|
304
|
-
declare function blockReplaceRange(block: AnyBlock, startNodeIndex: number, startOffset: number, endNodeIndex: number, endOffset: number, incoming: Node): Result<AnyBlock>;
|
|
305
|
-
|
|
306
|
-
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, createHistory, currentBlocks, deleteBlock, deleteLastChar, deleteRange, deserialize, deserializeNodes, duplicateBlock, 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/
|
|
1
|
+
// src/engine/block.ts
|
|
2
2
|
import { Result } from "@reiwuzen/result";
|
|
3
3
|
import { v7 } from "uuid";
|
|
4
4
|
function generateId(fn) {
|
|
@@ -37,12 +37,28 @@ function createBlock(type, idFn) {
|
|
|
37
37
|
return block;
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
|
-
function
|
|
41
|
-
return createBlock(type, idFn).
|
|
40
|
+
function createBlockAfter(blocks, afterId, type, idFn) {
|
|
41
|
+
return createBlock(type, idFn).andThen((newBlock) => {
|
|
42
42
|
const index = blocks.findIndex((b) => b.id === afterId);
|
|
43
|
+
if (index === -1) return Result.Err(`[BlockNotFound]: ${afterId}`);
|
|
43
44
|
const next = [...blocks];
|
|
44
45
|
next.splice(index + 1, 0, newBlock);
|
|
45
|
-
return { blocks: next, newId: newBlock.id };
|
|
46
|
+
return Result.Ok({ blocks: next, newId: newBlock.id });
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function insertBlockAfter(blocks, afterId, insertBlock) {
|
|
50
|
+
const targetBlockIndex = blocks.findIndex((b) => b.id === afterId);
|
|
51
|
+
if (targetBlockIndex === -1) {
|
|
52
|
+
return Result.Err(`No block found with id: ${afterId}`);
|
|
53
|
+
}
|
|
54
|
+
const newBlocks = [
|
|
55
|
+
...blocks.slice(0, targetBlockIndex + 1),
|
|
56
|
+
insertBlock,
|
|
57
|
+
...blocks.slice(targetBlockIndex + 1)
|
|
58
|
+
];
|
|
59
|
+
return Result.Ok({
|
|
60
|
+
blocks: newBlocks,
|
|
61
|
+
newFocusId: insertBlock.id
|
|
46
62
|
});
|
|
47
63
|
}
|
|
48
64
|
function deleteBlock(blocks, id) {
|
|
@@ -50,8 +66,19 @@ function deleteBlock(blocks, id) {
|
|
|
50
66
|
const prevId = blocks[index - 1]?.id ?? blocks[index + 1]?.id ?? "";
|
|
51
67
|
return { blocks: blocks.filter((b) => b.id !== id), prevId };
|
|
52
68
|
}
|
|
53
|
-
function duplicateBlock(incoming, newId) {
|
|
54
|
-
return {
|
|
69
|
+
function duplicateBlock(incoming, newId = crypto.randomUUID()) {
|
|
70
|
+
return {
|
|
71
|
+
...incoming,
|
|
72
|
+
id: newId,
|
|
73
|
+
meta: { ...incoming.meta },
|
|
74
|
+
content: incoming.content.map((node) => ({ ...node }))
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function duplicateBlockAfter(blocks, id, newId) {
|
|
78
|
+
const targetBlock = blocks.find((b) => b.id === id);
|
|
79
|
+
if (targetBlock == null) return Result.Err(`[BlockNotFound]: ${id}`);
|
|
80
|
+
const dup = duplicateBlock(targetBlock, newId);
|
|
81
|
+
return insertBlockAfter(blocks, id, dup);
|
|
55
82
|
}
|
|
56
83
|
function moveBlock(blocks, id, direction) {
|
|
57
84
|
const index = blocks.findIndex((b) => b.id === id);
|
|
@@ -367,25 +394,56 @@ function splitBlock(block, nodeIndex, offset) {
|
|
|
367
394
|
const targetLen = target ? getTextLength(target) : 0;
|
|
368
395
|
if (offset < 0 || offset > targetLen)
|
|
369
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;
|
|
370
399
|
const beforeNodes = [
|
|
371
400
|
...nodes.slice(0, nodeIndex),
|
|
372
|
-
...target && offset > 0 ? [sliceNode(target, 0, offset)].filter(
|
|
401
|
+
...target && offset > 0 ? [sliceNode(target, 0, offset)].filter(isNode) : []
|
|
373
402
|
];
|
|
374
403
|
const afterNodes = [
|
|
375
|
-
...target && offset < targetLen ? [sliceNode(target, offset, targetLen)].filter(
|
|
404
|
+
...target && offset < targetLen ? [sliceNode(target, offset, targetLen)].filter(isNode) : [],
|
|
376
405
|
...nodes.slice(nodeIndex + 1)
|
|
377
406
|
];
|
|
378
|
-
|
|
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 = {
|
|
379
434
|
...block,
|
|
380
435
|
content: beforeNodes
|
|
381
436
|
};
|
|
382
|
-
const
|
|
437
|
+
const right = {
|
|
383
438
|
id: generateId(),
|
|
384
439
|
type: "paragraph",
|
|
385
440
|
content: afterNodes,
|
|
386
441
|
meta: {}
|
|
387
442
|
};
|
|
388
|
-
return Result3.Ok([
|
|
443
|
+
return Result3.Ok([left, right]);
|
|
444
|
+
}
|
|
445
|
+
function isNode(node) {
|
|
446
|
+
return node !== null;
|
|
389
447
|
}
|
|
390
448
|
function mergeBlocks(blockA, blockB) {
|
|
391
449
|
if (blockA.type === "code" || blockA.type === "equation")
|
|
@@ -439,18 +497,6 @@ function stripPrefix(block, prefix) {
|
|
|
439
497
|
if (stripped.length === 0) return [];
|
|
440
498
|
return [{ ...first, text: stripped }, ...block.content.slice(1)];
|
|
441
499
|
}
|
|
442
|
-
function buildMeta(type) {
|
|
443
|
-
switch (type) {
|
|
444
|
-
case "bullet":
|
|
445
|
-
case "number":
|
|
446
|
-
case "todo":
|
|
447
|
-
return { depth: 0 };
|
|
448
|
-
case "heading1":
|
|
449
|
-
case "heading2":
|
|
450
|
-
case "heading3":
|
|
451
|
-
return {};
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
500
|
function applyMarkdownTransform(block, cursorOffset) {
|
|
455
501
|
if (block.type !== "paragraph")
|
|
456
502
|
return Result4.Ok({ block, converted: false });
|
|
@@ -467,7 +513,7 @@ function applyMarkdownTransform(block, cursorOffset) {
|
|
|
467
513
|
id: block.id,
|
|
468
514
|
type: targetType,
|
|
469
515
|
content: strippedContent,
|
|
470
|
-
meta:
|
|
516
|
+
meta: buildMetaForTarget(targetType)
|
|
471
517
|
};
|
|
472
518
|
return Result4.Ok({ block: converted, converted: true });
|
|
473
519
|
}
|
|
@@ -694,67 +740,8 @@ ${block.content[0].text}
|
|
|
694
740
|
}).join("\n\n");
|
|
695
741
|
}
|
|
696
742
|
|
|
697
|
-
// src/engine/cursor.ts
|
|
698
|
-
import { Result as Result6 } from "@reiwuzen/result";
|
|
699
|
-
function getTextLength2(node) {
|
|
700
|
-
if (node.type === "text" || node.type === "code") return node.text.length;
|
|
701
|
-
if (node.type === "equation") return node.latex.length;
|
|
702
|
-
return 0;
|
|
703
|
-
}
|
|
704
|
-
function flatToPosition(block, flatOffset) {
|
|
705
|
-
if (block.type === "code" || block.type === "equation") {
|
|
706
|
-
const node = block.content[0];
|
|
707
|
-
const len = getTextLength2(node);
|
|
708
|
-
if (flatOffset < 0 || flatOffset > len)
|
|
709
|
-
return Result6.Err(`flatOffset (${flatOffset}) out of bounds (length=${len})`);
|
|
710
|
-
return Result6.Ok({ nodeIndex: 0, offset: flatOffset });
|
|
711
|
-
}
|
|
712
|
-
const nodes = block.content;
|
|
713
|
-
if (flatOffset < 0)
|
|
714
|
-
return Result6.Err(`flatOffset (${flatOffset}) cannot be negative`);
|
|
715
|
-
let accumulated = 0;
|
|
716
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
717
|
-
const len = getTextLength2(nodes[i]);
|
|
718
|
-
if (flatOffset <= accumulated + len) {
|
|
719
|
-
return Result6.Ok({ nodeIndex: i, offset: flatOffset - accumulated });
|
|
720
|
-
}
|
|
721
|
-
accumulated += len;
|
|
722
|
-
}
|
|
723
|
-
if (flatOffset === accumulated) {
|
|
724
|
-
const last = nodes.length - 1;
|
|
725
|
-
return Result6.Ok({ nodeIndex: Math.max(0, last), offset: getTextLength2(nodes[last]) });
|
|
726
|
-
}
|
|
727
|
-
return Result6.Err(
|
|
728
|
-
`flatOffset (${flatOffset}) out of bounds (total length=${accumulated})`
|
|
729
|
-
);
|
|
730
|
-
}
|
|
731
|
-
function flatToSelection(block, start, end) {
|
|
732
|
-
if (start > end)
|
|
733
|
-
return Result6.Err(`start (${start}) cannot be greater than end (${end})`);
|
|
734
|
-
return flatToPosition(block, start).andThen(
|
|
735
|
-
(startPos) => flatToPosition(block, end).map((endPos) => ({
|
|
736
|
-
startIndex: startPos.nodeIndex,
|
|
737
|
-
startOffset: startPos.offset,
|
|
738
|
-
endIndex: endPos.nodeIndex,
|
|
739
|
-
endOffset: endPos.offset
|
|
740
|
-
}))
|
|
741
|
-
);
|
|
742
|
-
}
|
|
743
|
-
function positionToFlat(block, nodeIndex, offset) {
|
|
744
|
-
if (block.type === "code" || block.type === "equation")
|
|
745
|
-
return Result6.Ok(offset);
|
|
746
|
-
const nodes = block.content;
|
|
747
|
-
if (nodeIndex < 0 || nodeIndex >= nodes.length)
|
|
748
|
-
return Result6.Err(`nodeIndex (${nodeIndex}) out of bounds`);
|
|
749
|
-
let flat = 0;
|
|
750
|
-
for (let i = 0; i < nodeIndex; i++) {
|
|
751
|
-
flat += getTextLength2(nodes[i]);
|
|
752
|
-
}
|
|
753
|
-
return Result6.Ok(flat + offset);
|
|
754
|
-
}
|
|
755
|
-
|
|
756
743
|
// src/engine/history.ts
|
|
757
|
-
import { Result as
|
|
744
|
+
import { Result as Result6 } from "@reiwuzen/result";
|
|
758
745
|
function createHistory(initialBlocks) {
|
|
759
746
|
return {
|
|
760
747
|
past: [],
|
|
@@ -772,9 +759,9 @@ function push(history, blocks, maxSize = 100) {
|
|
|
772
759
|
}
|
|
773
760
|
function undo(history) {
|
|
774
761
|
if (history.past.length === 0)
|
|
775
|
-
return
|
|
762
|
+
return Result6.Err("Nothing to undo");
|
|
776
763
|
const previous = history.past[history.past.length - 1];
|
|
777
|
-
return
|
|
764
|
+
return Result6.Ok({
|
|
778
765
|
past: history.past.slice(0, -1),
|
|
779
766
|
present: previous,
|
|
780
767
|
future: [history.present, ...history.future]
|
|
@@ -782,9 +769,9 @@ function undo(history) {
|
|
|
782
769
|
}
|
|
783
770
|
function redo(history) {
|
|
784
771
|
if (history.future.length === 0)
|
|
785
|
-
return
|
|
772
|
+
return Result6.Err("Nothing to redo");
|
|
786
773
|
const next = history.future[0];
|
|
787
|
-
return
|
|
774
|
+
return Result6.Ok({
|
|
788
775
|
past: [...history.past, history.present],
|
|
789
776
|
present: next,
|
|
790
777
|
future: history.future.slice(1)
|
|
@@ -793,53 +780,13 @@ function redo(history) {
|
|
|
793
780
|
var canUndo = (history) => history.past.length > 0;
|
|
794
781
|
var canRedo = (history) => history.future.length > 0;
|
|
795
782
|
var currentBlocks = (history) => history.present.blocks;
|
|
796
|
-
|
|
797
|
-
// src/engine/block.ts
|
|
798
|
-
function blockInsertAt(block, nodeIndex, offset, incoming) {
|
|
799
|
-
return insertAt(block, nodeIndex, offset, incoming).map(
|
|
800
|
-
(content) => ({
|
|
801
|
-
...block,
|
|
802
|
-
content
|
|
803
|
-
})
|
|
804
|
-
);
|
|
805
|
-
}
|
|
806
|
-
function blockDeleteLastChar(block) {
|
|
807
|
-
return deleteLastChar(block).map(
|
|
808
|
-
(content) => ({
|
|
809
|
-
...block,
|
|
810
|
-
content
|
|
811
|
-
})
|
|
812
|
-
);
|
|
813
|
-
}
|
|
814
|
-
function blockDeleteRange(block, startNodeIndex, startOffset, endNodeIndex, endOffset) {
|
|
815
|
-
return deleteRange(
|
|
816
|
-
block,
|
|
817
|
-
startNodeIndex,
|
|
818
|
-
startOffset,
|
|
819
|
-
endNodeIndex,
|
|
820
|
-
endOffset
|
|
821
|
-
).map((content) => ({ ...block, content }));
|
|
822
|
-
}
|
|
823
|
-
function blockReplaceRange(block, startNodeIndex, startOffset, endNodeIndex, endOffset, incoming) {
|
|
824
|
-
return replaceRange(
|
|
825
|
-
block,
|
|
826
|
-
startNodeIndex,
|
|
827
|
-
startOffset,
|
|
828
|
-
endNodeIndex,
|
|
829
|
-
endOffset,
|
|
830
|
-
incoming
|
|
831
|
-
).map((content) => ({ ...block, content }));
|
|
832
|
-
}
|
|
833
783
|
export {
|
|
834
784
|
applyMarkdownTransform,
|
|
835
|
-
blockDeleteLastChar,
|
|
836
|
-
blockDeleteRange,
|
|
837
|
-
blockInsertAt,
|
|
838
|
-
blockReplaceRange,
|
|
839
785
|
canRedo,
|
|
840
786
|
canUndo,
|
|
841
787
|
changeBlockType,
|
|
842
788
|
createBlock,
|
|
789
|
+
createBlockAfter,
|
|
843
790
|
createHistory,
|
|
844
791
|
currentBlocks,
|
|
845
792
|
deleteBlock,
|
|
@@ -848,8 +795,7 @@ export {
|
|
|
848
795
|
deserialize,
|
|
849
796
|
deserializeNodes,
|
|
850
797
|
duplicateBlock,
|
|
851
|
-
|
|
852
|
-
flatToSelection,
|
|
798
|
+
duplicateBlockAfter,
|
|
853
799
|
formatNodes,
|
|
854
800
|
generateId,
|
|
855
801
|
indentBlock,
|
|
@@ -859,7 +805,6 @@ export {
|
|
|
859
805
|
mergeBlocks,
|
|
860
806
|
moveBlock,
|
|
861
807
|
outdentBlock,
|
|
862
|
-
positionToFlat,
|
|
863
808
|
push,
|
|
864
809
|
redo,
|
|
865
810
|
removeLink,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reiwuzen/blocky",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
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",
|