@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.
@@ -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"}