@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 +43 -134
- package/dist/index.d.cts +2 -46
- package/dist/index.d.ts +2 -46
- package/dist/index.js +43 -127
- package/package.json +1 -1
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/
|
|
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(
|
|
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(
|
|
471
|
+
...target && offset < targetLen ? [sliceNode(target, offset, targetLen)].filter(isNode) : [],
|
|
477
472
|
...nodes.slice(nodeIndex + 1)
|
|
478
473
|
];
|
|
479
|
-
|
|
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
|
|
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([
|
|
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:
|
|
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
|
|
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
|
|
829
|
+
return import_result6.Result.Err("Nothing to undo");
|
|
877
830
|
const previous = history.past[history.past.length - 1];
|
|
878
|
-
return
|
|
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
|
|
839
|
+
return import_result6.Result.Err("Nothing to redo");
|
|
887
840
|
const next = history.future[0];
|
|
888
|
-
return
|
|
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<
|
|
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
|
-
|
|
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<
|
|
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
|
-
|
|
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/
|
|
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(
|
|
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(
|
|
404
|
+
...target && offset < targetLen ? [sliceNode(target, offset, targetLen)].filter(isNode) : [],
|
|
403
405
|
...nodes.slice(nodeIndex + 1)
|
|
404
406
|
];
|
|
405
|
-
|
|
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
|
|
437
|
+
const right = {
|
|
410
438
|
id: generateId(),
|
|
411
439
|
type: "paragraph",
|
|
412
440
|
content: afterNodes,
|
|
413
441
|
meta: {}
|
|
414
442
|
};
|
|
415
|
-
return Result3.Ok([
|
|
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:
|
|
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
|
|
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
|
|
762
|
+
return Result6.Err("Nothing to undo");
|
|
803
763
|
const previous = history.past[history.past.length - 1];
|
|
804
|
-
return
|
|
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
|
|
772
|
+
return Result6.Err("Nothing to redo");
|
|
813
773
|
const next = history.future[0];
|
|
814
|
-
return
|
|
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.
|
|
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",
|