@reiwuzen/blocky 1.0.0
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/README.md +250 -0
- package/dist/engine/content.d.ts +46 -0
- package/dist/engine/content.d.ts.map +1 -0
- package/dist/engine/content.js +317 -0
- package/dist/engine/content.js.map +1 -0
- package/dist/engine/format.d.ts +26 -0
- package/dist/engine/format.d.ts.map +1 -0
- package/dist/engine/format.js +116 -0
- package/dist/engine/format.js.map +1 -0
- package/dist/engine/history.d.ts +35 -0
- package/dist/engine/history.d.ts.map +1 -0
- package/dist/engine/history.js +62 -0
- package/dist/engine/history.js.map +1 -0
- package/dist/engine/serializer.d.ts +46 -0
- package/dist/engine/serializer.d.ts.map +1 -0
- package/dist/engine/serializer.js +205 -0
- package/dist/engine/serializer.js.map +1 -0
- package/dist/engine/transform.d.ts +47 -0
- package/dist/engine/transform.d.ts.map +1 -0
- package/dist/engine/transform.js +195 -0
- package/dist/engine/transform.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/types/block.d.ts +44 -0
- package/dist/types/block.d.ts.map +1 -0
- package/dist/types/block.js +2 -0
- package/dist/types/block.js.map +1 -0
- package/dist/types/editor.d.ts +1 -0
- package/dist/types/editor.d.ts.map +1 -0
- package/dist/types/editor.js +2 -0
- package/dist/types/editor.js.map +1 -0
- package/dist/utils/block.d.ts +32 -0
- package/dist/utils/block.d.ts.map +1 -0
- package/dist/utils/block.js +97 -0
- package/dist/utils/block.js.map +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Result } from '@reiwuzen/result';
|
|
2
|
+
// ─── Validation ────────────────────────────────────────────────────────────────
|
|
3
|
+
function validateSelection(nodes, sel) {
|
|
4
|
+
const { startIndex, startOffset, endIndex, endOffset } = sel;
|
|
5
|
+
if (startIndex < 0 || endIndex >= nodes.length)
|
|
6
|
+
return Result.Err(`indices [${startIndex}, ${endIndex}] out of bounds (length=${nodes.length})`);
|
|
7
|
+
if (startIndex > endIndex)
|
|
8
|
+
return Result.Err(`startIndex (${startIndex}) > endIndex (${endIndex})`);
|
|
9
|
+
const startNode = nodes[startIndex];
|
|
10
|
+
const endNode = nodes[endIndex];
|
|
11
|
+
if (!isTextNode(startNode) || !isTextNode(endNode))
|
|
12
|
+
return Result.Err(`Selection must start and end on a text node`);
|
|
13
|
+
if (startOffset < 0 || startOffset > startNode.text.length)
|
|
14
|
+
return Result.Err(`startOffset (${startOffset}) out of bounds for "${startNode.text}"`);
|
|
15
|
+
if (endOffset < 0 || endOffset > endNode.text.length)
|
|
16
|
+
return Result.Err(`endOffset (${endOffset}) out of bounds for "${endNode.text}"`);
|
|
17
|
+
if (startIndex === endIndex && startOffset >= endOffset)
|
|
18
|
+
return Result.Err(`startOffset (${startOffset}) must be < endOffset (${endOffset}) within same node`);
|
|
19
|
+
if (!nodes.slice(startIndex, endIndex + 1).every(isTextNode))
|
|
20
|
+
return Result.Err(`Selection contains non-text nodes (code/equation)`);
|
|
21
|
+
return Result.Ok(undefined);
|
|
22
|
+
}
|
|
23
|
+
// ─── Helpers ───────────────────────────────────────────────────────────────────
|
|
24
|
+
export function isTextNode(node) {
|
|
25
|
+
return node.type === "text";
|
|
26
|
+
}
|
|
27
|
+
function isFormatActive(nodes, sel, format, value = true) {
|
|
28
|
+
return nodes
|
|
29
|
+
.slice(sel.startIndex, sel.endIndex + 1)
|
|
30
|
+
.every((n) => n[format] === value);
|
|
31
|
+
}
|
|
32
|
+
function applyFormat(node, format, value, remove) {
|
|
33
|
+
const next = { ...node };
|
|
34
|
+
if (remove) {
|
|
35
|
+
delete next[format];
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
next[format] = value;
|
|
39
|
+
}
|
|
40
|
+
return next;
|
|
41
|
+
}
|
|
42
|
+
function splitNode(node, start, end, format, value, remove) {
|
|
43
|
+
const parts = [];
|
|
44
|
+
if (start > 0)
|
|
45
|
+
parts.push({ ...node, text: node.text.slice(0, start) });
|
|
46
|
+
parts.push(applyFormat({ ...node, text: node.text.slice(start, end) }, format, value, remove));
|
|
47
|
+
if (end < node.text.length)
|
|
48
|
+
parts.push({ ...node, text: node.text.slice(end) });
|
|
49
|
+
return parts;
|
|
50
|
+
}
|
|
51
|
+
// ─── Core Engine ───────────────────────────────────────────────────────────────
|
|
52
|
+
export function formatNodes(nodes, sel, format, value = true) {
|
|
53
|
+
return validateSelection(nodes, sel).map(() => {
|
|
54
|
+
const { startIndex, startOffset, endIndex, endOffset } = sel;
|
|
55
|
+
const textNodes = nodes;
|
|
56
|
+
const remove = isFormatActive(textNodes, sel, format, value);
|
|
57
|
+
const result = [];
|
|
58
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
59
|
+
const node = nodes[i];
|
|
60
|
+
if (i < startIndex || i > endIndex) {
|
|
61
|
+
result.push(node);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (!isTextNode(node)) {
|
|
65
|
+
result.push(node);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const isSingle = startIndex === endIndex;
|
|
69
|
+
const isFirst = i === startIndex;
|
|
70
|
+
const isLast = i === endIndex;
|
|
71
|
+
if (isSingle) {
|
|
72
|
+
result.push(...splitNode(node, startOffset, endOffset, format, value, remove));
|
|
73
|
+
}
|
|
74
|
+
else if (isFirst) {
|
|
75
|
+
result.push(...splitNode(node, startOffset, node.text.length, format, value, remove));
|
|
76
|
+
}
|
|
77
|
+
else if (isLast) {
|
|
78
|
+
result.push(...splitNode(node, 0, endOffset, format, value, remove));
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
result.push(applyFormat(node, format, value, remove));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return mergeAdjacentNodes(result);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
// ─── Merge adjacent nodes ──────────────────────────────────────────────────────
|
|
88
|
+
export function mergeAdjacentNodes(nodes) {
|
|
89
|
+
const result = [];
|
|
90
|
+
for (const node of nodes) {
|
|
91
|
+
const prev = result[result.length - 1];
|
|
92
|
+
if (prev && isTextNode(prev) && isTextNode(node) && formatsMatch(prev, node)) {
|
|
93
|
+
result[result.length - 1] = { ...prev, text: prev.text + node.text };
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
result.push(node);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
export function formatsMatch(a, b) {
|
|
102
|
+
const keys = [
|
|
103
|
+
"bold", "italic", "underline", "strikethrough", "highlighted", "color", "link",
|
|
104
|
+
];
|
|
105
|
+
return keys.every((k) => a[k] === b[k]);
|
|
106
|
+
}
|
|
107
|
+
// ─── Convenience wrappers ──────────────────────────────────────────────────────
|
|
108
|
+
export const toggleBold = (nodes, sel) => formatNodes(nodes, sel, "bold");
|
|
109
|
+
export const toggleItalic = (nodes, sel) => formatNodes(nodes, sel, "italic");
|
|
110
|
+
export const toggleUnderline = (nodes, sel) => formatNodes(nodes, sel, "underline");
|
|
111
|
+
export const toggleStrikethrough = (nodes, sel) => formatNodes(nodes, sel, "strikethrough");
|
|
112
|
+
export const toggleHighlight = (nodes, sel, color = "yellow") => formatNodes(nodes, sel, "highlighted", color);
|
|
113
|
+
export const toggleColor = (nodes, sel, color) => formatNodes(nodes, sel, "color", color);
|
|
114
|
+
export const setLink = (nodes, sel, href) => formatNodes(nodes, sel, "link", href);
|
|
115
|
+
export const removeLink = (nodes, sel) => formatNodes(nodes, sel, "link", undefined);
|
|
116
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/engine/format.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AnyBlock } from '../types/block';
|
|
2
|
+
import { Result } from '@reiwuzen/result';
|
|
3
|
+
export type HistoryEntry = {
|
|
4
|
+
blocks: AnyBlock[];
|
|
5
|
+
timestamp: number;
|
|
6
|
+
};
|
|
7
|
+
export type History = {
|
|
8
|
+
past: HistoryEntry[];
|
|
9
|
+
present: HistoryEntry;
|
|
10
|
+
future: HistoryEntry[];
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Create a new history instance with the given initial blocks.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createHistory(initialBlocks: AnyBlock[]): History;
|
|
16
|
+
/**
|
|
17
|
+
* Push a new state onto the history stack.
|
|
18
|
+
* Clears the future — same as any editor after a new action post-undo.
|
|
19
|
+
* Optionally cap the max history size to avoid unbounded memory growth.
|
|
20
|
+
*/
|
|
21
|
+
export declare function push(history: History, blocks: AnyBlock[], maxSize?: number): History;
|
|
22
|
+
/**
|
|
23
|
+
* Move back one step in history.
|
|
24
|
+
* Returns Err if there is nothing to undo.
|
|
25
|
+
*/
|
|
26
|
+
export declare function undo(history: History): Result<History, string>;
|
|
27
|
+
/**
|
|
28
|
+
* Move forward one step in history.
|
|
29
|
+
* Returns Err if there is nothing to redo.
|
|
30
|
+
*/
|
|
31
|
+
export declare function redo(history: History): Result<History, string>;
|
|
32
|
+
export declare const canUndo: (history: History) => boolean;
|
|
33
|
+
export declare const canRedo: (history: History) => boolean;
|
|
34
|
+
export declare const currentBlocks: (history: History) => AnyBlock[];
|
|
35
|
+
//# sourceMappingURL=history.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/engine/history.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAI1C,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAK,YAAY,EAAE,CAAC;IACxB,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAG,YAAY,EAAE,CAAC;CACzB,CAAC;AAIF;;GAEG;AACH,wBAAgB,aAAa,CAAC,aAAa,EAAE,QAAQ,EAAE,GAAG,OAAO,CAMhE;AAID;;;;GAIG;AACH,wBAAgB,IAAI,CAClB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,QAAQ,EAAE,EAClB,OAAO,GAAE,MAAY,GACpB,OAAO,CAOT;AAID;;;GAGG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAW9D;AAID;;;GAGG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAW9D;AAID,eAAO,MAAM,OAAO,GAAI,SAAS,OAAO,KAAG,OAAkC,CAAC;AAC9E,eAAO,MAAM,OAAO,GAAI,SAAS,OAAO,KAAG,OAAoC,CAAC;AAIhF,eAAO,MAAM,aAAa,GAAI,SAAS,OAAO,KAAG,QAAQ,EAA4B,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Result } from '@reiwuzen/result';
|
|
2
|
+
// ─── createHistory ─────────────────────────────────────────────────────────────
|
|
3
|
+
/**
|
|
4
|
+
* Create a new history instance with the given initial blocks.
|
|
5
|
+
*/
|
|
6
|
+
export function createHistory(initialBlocks) {
|
|
7
|
+
return {
|
|
8
|
+
past: [],
|
|
9
|
+
present: { blocks: initialBlocks, timestamp: Date.now() },
|
|
10
|
+
future: [],
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
// ─── push ──────────────────────────────────────────────────────────────────────
|
|
14
|
+
/**
|
|
15
|
+
* Push a new state onto the history stack.
|
|
16
|
+
* Clears the future — same as any editor after a new action post-undo.
|
|
17
|
+
* Optionally cap the max history size to avoid unbounded memory growth.
|
|
18
|
+
*/
|
|
19
|
+
export function push(history, blocks, maxSize = 100) {
|
|
20
|
+
const past = [...history.past, history.present].slice(-maxSize);
|
|
21
|
+
return {
|
|
22
|
+
past,
|
|
23
|
+
present: { blocks, timestamp: Date.now() },
|
|
24
|
+
future: [],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
// ─── undo ──────────────────────────────────────────────────────────────────────
|
|
28
|
+
/**
|
|
29
|
+
* Move back one step in history.
|
|
30
|
+
* Returns Err if there is nothing to undo.
|
|
31
|
+
*/
|
|
32
|
+
export function undo(history) {
|
|
33
|
+
if (history.past.length === 0)
|
|
34
|
+
return Result.Err("Nothing to undo");
|
|
35
|
+
const previous = history.past[history.past.length - 1];
|
|
36
|
+
return Result.Ok({
|
|
37
|
+
past: history.past.slice(0, -1),
|
|
38
|
+
present: previous,
|
|
39
|
+
future: [history.present, ...history.future],
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
// ─── redo ──────────────────────────────────────────────────────────────────────
|
|
43
|
+
/**
|
|
44
|
+
* Move forward one step in history.
|
|
45
|
+
* Returns Err if there is nothing to redo.
|
|
46
|
+
*/
|
|
47
|
+
export function redo(history) {
|
|
48
|
+
if (history.future.length === 0)
|
|
49
|
+
return Result.Err("Nothing to redo");
|
|
50
|
+
const next = history.future[0];
|
|
51
|
+
return Result.Ok({
|
|
52
|
+
past: [...history.past, history.present],
|
|
53
|
+
present: next,
|
|
54
|
+
future: history.future.slice(1),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// ─── canUndo / canRedo ─────────────────────────────────────────────────────────
|
|
58
|
+
export const canUndo = (history) => history.past.length > 0;
|
|
59
|
+
export const canRedo = (history) => history.future.length > 0;
|
|
60
|
+
// ─── currentBlocks ─────────────────────────────────────────────────────────────
|
|
61
|
+
export const currentBlocks = (history) => history.present.blocks;
|
|
62
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.js","sourceRoot":"","sources":["../../src/engine/history.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAe1C,kFAAkF;AAElF;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,aAAyB;IACrD,OAAO;QACL,IAAI,EAAK,EAAE;QACX,OAAO,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE;QACzD,MAAM,EAAG,EAAE;KACZ,CAAC;AACJ,CAAC;AAED,kFAAkF;AAElF;;;;GAIG;AACH,MAAM,UAAU,IAAI,CAClB,OAAgB,EAChB,MAAkB,EAClB,UAAkB,GAAG;IAErB,MAAM,IAAI,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC;IAChE,OAAO;QACL,IAAI;QACJ,OAAO,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE;QAC1C,MAAM,EAAG,EAAE;KACZ,CAAC;AACJ,CAAC;AAED,kFAAkF;AAElF;;;GAGG;AACH,MAAM,UAAU,IAAI,CAAC,OAAgB;IACnC,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;QAC3B,OAAO,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAEvC,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAEvD,OAAO,MAAM,CAAC,EAAE,CAAC;QACf,IAAI,EAAK,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAClC,OAAO,EAAE,QAAQ;QACjB,MAAM,EAAG,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;KAC9C,CAAC,CAAC;AACL,CAAC;AAED,kFAAkF;AAElF;;;GAGG;AACH,MAAM,UAAU,IAAI,CAAC,OAAgB;IACnC,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QAC7B,OAAO,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAEvC,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAE/B,OAAO,MAAM,CAAC,EAAE,CAAC;QACf,IAAI,EAAK,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC;QAC3C,OAAO,EAAE,IAAI;QACb,MAAM,EAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;KACjC,CAAC,CAAC;AACL,CAAC;AAED,kFAAkF;AAElF,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,OAAgB,EAAW,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;AAC9E,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,OAAgB,EAAW,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;AAEhF,kFAAkF;AAElF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,OAAgB,EAAc,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Result } from '@reiwuzen/result';
|
|
2
|
+
import type { AnyBlock, Node } from '../types/block';
|
|
3
|
+
/**
|
|
4
|
+
* Serialize an array of blocks to a JSON string.
|
|
5
|
+
* Wraps JSON.stringify in Result.try so any circular ref or
|
|
6
|
+
* other stringify error surfaces as Err rather than throwing.
|
|
7
|
+
*/
|
|
8
|
+
export declare function serialize(blocks: AnyBlock[]): Result<string, unknown>;
|
|
9
|
+
/**
|
|
10
|
+
* Deserialize a JSON string back into AnyBlock[].
|
|
11
|
+
* Validates:
|
|
12
|
+
* - valid JSON
|
|
13
|
+
* - top level is an array
|
|
14
|
+
* - each item has id (string), type (known BlockType), meta (object), content (array)
|
|
15
|
+
*/
|
|
16
|
+
export declare function deserialize(json: string): Result<AnyBlock[], string>;
|
|
17
|
+
/**
|
|
18
|
+
* Serialize a Node[] to a JSON string — for clipboard copy.
|
|
19
|
+
* Preserves all formatting.
|
|
20
|
+
*/
|
|
21
|
+
export declare function serializeNodes(nodes: Node[]): Result<string, unknown>;
|
|
22
|
+
/**
|
|
23
|
+
* Deserialize a JSON string back into Node[] — for clipboard paste.
|
|
24
|
+
* Validates each node shape before returning.
|
|
25
|
+
*/
|
|
26
|
+
export declare function deserializeNodes(json: string): Result<Node[], string>;
|
|
27
|
+
/**
|
|
28
|
+
* Extract plain text from a Node[] — strips all formatting.
|
|
29
|
+
* text → text
|
|
30
|
+
* code → text
|
|
31
|
+
* equation → latex
|
|
32
|
+
*/
|
|
33
|
+
export declare function toPlainText(nodes: Node[]): string;
|
|
34
|
+
/**
|
|
35
|
+
* Convert an array of blocks to a markdown string.
|
|
36
|
+
*
|
|
37
|
+
* paragraph → plain inline
|
|
38
|
+
* heading1-3 → # / ## / ###
|
|
39
|
+
* bullet → - (indented by depth)
|
|
40
|
+
* number → 1. (indented by depth)
|
|
41
|
+
* todo → - [ ] / - [x] (indented by depth)
|
|
42
|
+
* code → ```language\n...\n```
|
|
43
|
+
* equation → $$...$$ (block equation)
|
|
44
|
+
*/
|
|
45
|
+
export declare function toMarkdown(blocks: AnyBlock[]): string;
|
|
46
|
+
//# sourceMappingURL=serializer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../../src/engine/serializer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,KAAK,EAAE,QAAQ,EAAa,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAIhE;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAErE;AAID;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,QAAQ,EAAE,EAAE,MAAM,CAAC,CAoCpE;AA2DD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAErE;AAID;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,EAAE,MAAM,CAAC,CAcrE;AAID;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CASjD;AA2BD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,CAwCrD"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { Result } from '@reiwuzen/result';
|
|
2
|
+
// ─── Serialize ─────────────────────────────────────────────────────────────────
|
|
3
|
+
/**
|
|
4
|
+
* Serialize an array of blocks to a JSON string.
|
|
5
|
+
* Wraps JSON.stringify in Result.try so any circular ref or
|
|
6
|
+
* other stringify error surfaces as Err rather than throwing.
|
|
7
|
+
*/
|
|
8
|
+
export function serialize(blocks) {
|
|
9
|
+
return Result.try(() => JSON.stringify(blocks));
|
|
10
|
+
}
|
|
11
|
+
// ─── Deserialize ───────────────────────────────────────────────────────────────
|
|
12
|
+
/**
|
|
13
|
+
* Deserialize a JSON string back into AnyBlock[].
|
|
14
|
+
* Validates:
|
|
15
|
+
* - valid JSON
|
|
16
|
+
* - top level is an array
|
|
17
|
+
* - each item has id (string), type (known BlockType), meta (object), content (array)
|
|
18
|
+
*/
|
|
19
|
+
export function deserialize(json) {
|
|
20
|
+
return Result.try(() => JSON.parse(json))
|
|
21
|
+
.mapErr(() => "Invalid JSON string")
|
|
22
|
+
.andThen((parsed) => {
|
|
23
|
+
if (!Array.isArray(parsed))
|
|
24
|
+
return Result.Err("Expected an array at top level");
|
|
25
|
+
const validTypes = new Set([
|
|
26
|
+
"paragraph", "heading1", "heading2", "heading3",
|
|
27
|
+
"bullet", "number", "todo", "code", "equation",
|
|
28
|
+
]);
|
|
29
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
30
|
+
const block = parsed[i];
|
|
31
|
+
if (typeof block !== "object" || block === null)
|
|
32
|
+
return Result.Err(`Block at index ${i} is not an object`);
|
|
33
|
+
if (typeof block.id !== "string" || !block.id.length)
|
|
34
|
+
return Result.Err(`Block at index ${i} has invalid id`);
|
|
35
|
+
if (!validTypes.has(block.type))
|
|
36
|
+
return Result.Err(`Block at index ${i} has unknown type "${block.type}"`);
|
|
37
|
+
if (typeof block.meta !== "object" || block.meta === null)
|
|
38
|
+
return Result.Err(`Block at index ${i} has invalid meta`);
|
|
39
|
+
if (!Array.isArray(block.content))
|
|
40
|
+
return Result.Err(`Block at index ${i} has invalid content`);
|
|
41
|
+
const contentErr = validateContent(block.content, block.type, i);
|
|
42
|
+
if (contentErr)
|
|
43
|
+
return Result.Err(contentErr);
|
|
44
|
+
}
|
|
45
|
+
return Result.Ok(parsed);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
// ─── Content validator ─────────────────────────────────────────────────────────
|
|
49
|
+
function validateContent(content, type, blockIndex) {
|
|
50
|
+
const isLeaf = type === "code" || type === "equation";
|
|
51
|
+
if (isLeaf && content.length !== 1)
|
|
52
|
+
return `Block at index ${blockIndex} (${type}) must have exactly 1 content node`;
|
|
53
|
+
for (let i = 0; i < content.length; i++) {
|
|
54
|
+
const node = content[i];
|
|
55
|
+
const err = validateNode(node, blockIndex, i);
|
|
56
|
+
if (err)
|
|
57
|
+
return err;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function validateNode(node, blockIndex, nodeIndex) {
|
|
62
|
+
const prefix = `Block[${blockIndex}].content[${nodeIndex}]`;
|
|
63
|
+
if (typeof node !== "object" || node === null)
|
|
64
|
+
return `${prefix} is not an object`;
|
|
65
|
+
const n = node;
|
|
66
|
+
if (n.type === "text") {
|
|
67
|
+
if (typeof n.text !== "string")
|
|
68
|
+
return `${prefix} text node missing "text" string`;
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
if (n.type === "code") {
|
|
72
|
+
if (typeof n.text !== "string")
|
|
73
|
+
return `${prefix} code node missing "text" string`;
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
if (n.type === "equation") {
|
|
77
|
+
if (typeof n.latex !== "string")
|
|
78
|
+
return `${prefix} equation node missing "latex" string`;
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
return `${prefix} has unknown node type "${n.type}"`;
|
|
82
|
+
}
|
|
83
|
+
// ─── serializeNodes ────────────────────────────────────────────────────────────
|
|
84
|
+
/**
|
|
85
|
+
* Serialize a Node[] to a JSON string — for clipboard copy.
|
|
86
|
+
* Preserves all formatting.
|
|
87
|
+
*/
|
|
88
|
+
export function serializeNodes(nodes) {
|
|
89
|
+
return Result.try(() => JSON.stringify(nodes));
|
|
90
|
+
}
|
|
91
|
+
// ─── deserializeNodes ──────────────────────────────────────────────────────────
|
|
92
|
+
/**
|
|
93
|
+
* Deserialize a JSON string back into Node[] — for clipboard paste.
|
|
94
|
+
* Validates each node shape before returning.
|
|
95
|
+
*/
|
|
96
|
+
export function deserializeNodes(json) {
|
|
97
|
+
return Result.try(() => JSON.parse(json))
|
|
98
|
+
.mapErr(() => "Invalid JSON string")
|
|
99
|
+
.andThen((parsed) => {
|
|
100
|
+
if (!Array.isArray(parsed))
|
|
101
|
+
return Result.Err("Expected an array of nodes");
|
|
102
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
103
|
+
const err = validateNode(parsed[i], 0, i);
|
|
104
|
+
if (err)
|
|
105
|
+
return Result.Err(err);
|
|
106
|
+
}
|
|
107
|
+
return Result.Ok(parsed);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
// ─── toPlainText ───────────────────────────────────────────────────────────────
|
|
111
|
+
/**
|
|
112
|
+
* Extract plain text from a Node[] — strips all formatting.
|
|
113
|
+
* text → text
|
|
114
|
+
* code → text
|
|
115
|
+
* equation → latex
|
|
116
|
+
*/
|
|
117
|
+
export function toPlainText(nodes) {
|
|
118
|
+
return nodes
|
|
119
|
+
.map((n) => {
|
|
120
|
+
if (n.type === "text")
|
|
121
|
+
return n.text;
|
|
122
|
+
if (n.type === "code")
|
|
123
|
+
return n.text;
|
|
124
|
+
if (n.type === "equation")
|
|
125
|
+
return n.latex;
|
|
126
|
+
return "";
|
|
127
|
+
})
|
|
128
|
+
.join("");
|
|
129
|
+
}
|
|
130
|
+
// ─── toMarkdown ────────────────────────────────────────────────────────────────
|
|
131
|
+
/**
|
|
132
|
+
* Convert a Node[] to a markdown inline string.
|
|
133
|
+
* Used internally by blocksToMarkdown per block.
|
|
134
|
+
*/
|
|
135
|
+
function nodesToMarkdown(nodes) {
|
|
136
|
+
return nodes
|
|
137
|
+
.map((n) => {
|
|
138
|
+
if (n.type === "code")
|
|
139
|
+
return `\`${n.text}\``;
|
|
140
|
+
if (n.type === "equation")
|
|
141
|
+
return `$${n.latex}$`;
|
|
142
|
+
// text node — apply formatting wrappers
|
|
143
|
+
let text = n.text;
|
|
144
|
+
if (n.bold)
|
|
145
|
+
text = `**${text}**`;
|
|
146
|
+
if (n.italic)
|
|
147
|
+
text = `*${text}*`;
|
|
148
|
+
if (n.underline)
|
|
149
|
+
text = `<u>${text}</u>`;
|
|
150
|
+
if (n.strikethrough)
|
|
151
|
+
text = `~~${text}~~`;
|
|
152
|
+
if (n.link)
|
|
153
|
+
text = `[${text}](${n.link})`;
|
|
154
|
+
return text;
|
|
155
|
+
})
|
|
156
|
+
.join("");
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Convert an array of blocks to a markdown string.
|
|
160
|
+
*
|
|
161
|
+
* paragraph → plain inline
|
|
162
|
+
* heading1-3 → # / ## / ###
|
|
163
|
+
* bullet → - (indented by depth)
|
|
164
|
+
* number → 1. (indented by depth)
|
|
165
|
+
* todo → - [ ] / - [x] (indented by depth)
|
|
166
|
+
* code → ```language\n...\n```
|
|
167
|
+
* equation → $$...$$ (block equation)
|
|
168
|
+
*/
|
|
169
|
+
export function toMarkdown(blocks) {
|
|
170
|
+
return blocks
|
|
171
|
+
.map((block) => {
|
|
172
|
+
var _a;
|
|
173
|
+
switch (block.type) {
|
|
174
|
+
case "paragraph":
|
|
175
|
+
return nodesToMarkdown(block.content);
|
|
176
|
+
case "heading1":
|
|
177
|
+
return `# ${nodesToMarkdown(block.content)}`;
|
|
178
|
+
case "heading2":
|
|
179
|
+
return `## ${nodesToMarkdown(block.content)}`;
|
|
180
|
+
case "heading3":
|
|
181
|
+
return `### ${nodesToMarkdown(block.content)}`;
|
|
182
|
+
case "bullet": {
|
|
183
|
+
const indent = " ".repeat(block.meta.depth);
|
|
184
|
+
return `${indent}- ${nodesToMarkdown(block.content)}`;
|
|
185
|
+
}
|
|
186
|
+
case "number": {
|
|
187
|
+
const indent = " ".repeat(block.meta.depth);
|
|
188
|
+
return `${indent}1. ${nodesToMarkdown(block.content)}`;
|
|
189
|
+
}
|
|
190
|
+
case "todo": {
|
|
191
|
+
const indent = " ".repeat(block.meta.depth);
|
|
192
|
+
const checkbox = block.meta.checked ? "[x]" : "[ ]";
|
|
193
|
+
return `${indent}- ${checkbox} ${nodesToMarkdown(block.content)}`;
|
|
194
|
+
}
|
|
195
|
+
case "code": {
|
|
196
|
+
const lang = (_a = block.meta.language) !== null && _a !== void 0 ? _a : "";
|
|
197
|
+
return `\`\`\`${lang}\n${block.content[0].text}\n\`\`\``;
|
|
198
|
+
}
|
|
199
|
+
case "equation":
|
|
200
|
+
return `$$${block.content[0].latex}$$`;
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
.join("\n\n");
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=serializer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serializer.js","sourceRoot":"","sources":["../../src/engine/serializer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { AnyBlock, Block, BlockType } from '../types/block';
|
|
2
|
+
import { Result } from '@reiwuzen/result';
|
|
3
|
+
export type TransformResult = {
|
|
4
|
+
block: AnyBlock;
|
|
5
|
+
converted: boolean;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Call this when a space is typed.
|
|
9
|
+
*
|
|
10
|
+
* Guards (returns converted=false if any fail):
|
|
11
|
+
* 1. block.type must be "paragraph"
|
|
12
|
+
* 2. cursorOffset must be ≤ trigger.length + 1
|
|
13
|
+
* — ensures the space was typed right after the trigger at position 0
|
|
14
|
+
* — blocks mid-text spaces like "hello - " from converting
|
|
15
|
+
* 3. text must start with a known trigger prefix
|
|
16
|
+
*/
|
|
17
|
+
export declare function applyMarkdownTransform(block: AnyBlock, cursorOffset: number): Result<TransformResult>;
|
|
18
|
+
/**
|
|
19
|
+
* Convert a block to a new type while preserving content as much as possible.
|
|
20
|
+
*
|
|
21
|
+
* rich → rich keep content as-is, update type + meta
|
|
22
|
+
* rich → code strip all formatting, concat all text → [CodeNode]
|
|
23
|
+
* rich → equation strip all formatting, concat all text → [EquationNode]
|
|
24
|
+
* code → rich single TextNode with code.text
|
|
25
|
+
* equation → rich single TextNode with equation.latex
|
|
26
|
+
* code → equation [EquationNode] with code.text as latex
|
|
27
|
+
* equation → code [CodeNode] with equation.latex as text
|
|
28
|
+
*/
|
|
29
|
+
export declare function changeBlockType<T extends BlockType>(block: AnyBlock, targetType: T): Result<Block<T>>;
|
|
30
|
+
/**
|
|
31
|
+
* Toggle the checked state of a todo block.
|
|
32
|
+
* checked: undefined → checked: true → checked: undefined (cycle)
|
|
33
|
+
*/
|
|
34
|
+
export declare function toggleTodo(block: AnyBlock): Result<Block<"todo">>;
|
|
35
|
+
type IndentableBlock = Block<"bullet"> | Block<"number"> | Block<"todo">;
|
|
36
|
+
/**
|
|
37
|
+
* Increase the depth of a bullet, number, or todo block by 1.
|
|
38
|
+
* Max depth is 6. Returns Err if block type is not indentable.
|
|
39
|
+
*/
|
|
40
|
+
export declare function indentBlock(block: AnyBlock): Result<IndentableBlock>;
|
|
41
|
+
/**
|
|
42
|
+
* Decrease the depth of a bullet, number, or todo block by 1.
|
|
43
|
+
* Min depth is 0. Returns Err if block type is not indentable or already at 0.
|
|
44
|
+
*/
|
|
45
|
+
export declare function outdentBlock(block: AnyBlock): Result<IndentableBlock>;
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=transform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../src/engine/transform.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAQ,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAQ1C,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,QAAQ,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC;AA8CF;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,QAAQ,EACf,YAAY,EAAE,MAAM,GACnB,MAAM,CAAC,eAAe,CAAC,CAkCzB;AAKD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,SAAS,EACjD,KAAK,EAAE,QAAQ,EACf,UAAU,EAAE,CAAC,GACZ,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAalB;AA6DD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAWjE;AAKD,KAAK,eAAe,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;AASzE;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAAC,eAAe,CAAC,CAWpE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAAC,eAAe,CAAC,CAWrE"}
|