@reiwuzen/blocky 1.1.1 → 1.2.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/dist/index.cjs +954 -0
- package/dist/index.d.cts +306 -0
- package/dist/index.d.ts +306 -11
- package/dist/index.js +881 -15
- package/package.json +32 -25
- package/dist/engine/block.d.ts +0 -13
- package/dist/engine/block.d.ts.map +0 -1
- package/dist/engine/block.js +0 -26
- package/dist/engine/block.js.map +0 -1
- package/dist/engine/content.d.ts +0 -46
- package/dist/engine/content.d.ts.map +0 -1
- package/dist/engine/content.js +0 -317
- package/dist/engine/content.js.map +0 -1
- package/dist/engine/cursor.d.ts +0 -38
- package/dist/engine/cursor.d.ts.map +0 -1
- package/dist/engine/cursor.js +0 -90
- package/dist/engine/cursor.js.map +0 -1
- package/dist/engine/format.d.ts +0 -26
- package/dist/engine/format.d.ts.map +0 -1
- package/dist/engine/format.js +0 -116
- package/dist/engine/format.js.map +0 -1
- package/dist/engine/history.d.ts +0 -35
- package/dist/engine/history.d.ts.map +0 -1
- package/dist/engine/history.js +0 -62
- package/dist/engine/history.js.map +0 -1
- package/dist/engine/serializer.d.ts +0 -46
- package/dist/engine/serializer.d.ts.map +0 -1
- package/dist/engine/serializer.js +0 -205
- package/dist/engine/serializer.js.map +0 -1
- package/dist/engine/transform.d.ts +0 -47
- package/dist/engine/transform.d.ts.map +0 -1
- package/dist/engine/transform.js +0 -195
- package/dist/engine/transform.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/types/block.d.ts +0 -44
- package/dist/types/block.d.ts.map +0 -1
- package/dist/types/block.js +0 -2
- package/dist/types/block.js.map +0 -1
- package/dist/types/editor.d.ts +0 -1
- package/dist/types/editor.d.ts.map +0 -1
- package/dist/types/editor.js +0 -2
- package/dist/types/editor.js.map +0 -1
- package/dist/utils/block.d.ts +0 -32
- package/dist/utils/block.d.ts.map +0 -1
- package/dist/utils/block.js +0 -97
- package/dist/utils/block.js.map +0 -1
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { Result } from '@reiwuzen/result';
|
|
2
|
+
|
|
3
|
+
type BlockType = "paragraph" | "heading1" | "heading2" | "heading3" | "bullet" | "number" | "todo" | "equation" | "code";
|
|
4
|
+
type Node = {
|
|
5
|
+
type: "text";
|
|
6
|
+
text: string;
|
|
7
|
+
highlighted?: "yellow" | "green";
|
|
8
|
+
color?: "red" | 'blue' | 'green';
|
|
9
|
+
bold?: true;
|
|
10
|
+
italic?: true;
|
|
11
|
+
underline?: true;
|
|
12
|
+
strikethrough?: true;
|
|
13
|
+
link?: string;
|
|
14
|
+
} | {
|
|
15
|
+
type: "code";
|
|
16
|
+
text: string;
|
|
17
|
+
} | {
|
|
18
|
+
type: "equation";
|
|
19
|
+
latex: string;
|
|
20
|
+
};
|
|
21
|
+
type BlockMeta<T extends BlockType> = T extends "todo" ? {
|
|
22
|
+
checked?: true;
|
|
23
|
+
depth: number;
|
|
24
|
+
} : T extends "bullet" | "number" ? {
|
|
25
|
+
depth: number;
|
|
26
|
+
} : T extends "code" ? {
|
|
27
|
+
language?: string;
|
|
28
|
+
} : Record<string, never>;
|
|
29
|
+
type BlockContent<T extends BlockType> = T extends "code" ? [Extract<Node, {
|
|
30
|
+
type: "code";
|
|
31
|
+
}>] : T extends "equation" ? [Extract<Node, {
|
|
32
|
+
type: "equation";
|
|
33
|
+
}>] : Node[];
|
|
34
|
+
type Block<T extends BlockType = BlockType> = {
|
|
35
|
+
id: string;
|
|
36
|
+
type: T;
|
|
37
|
+
meta: BlockMeta<T>;
|
|
38
|
+
content: BlockContent<T>;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Discriminated Union of Block
|
|
42
|
+
*/
|
|
43
|
+
type AnyBlock = {
|
|
44
|
+
[K in BlockType]: Block<K>;
|
|
45
|
+
}[BlockType];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Insert a Node at a specific position within block content.
|
|
49
|
+
*
|
|
50
|
+
* - nodeIndex: which node to insert into
|
|
51
|
+
* - offset: char position within that node
|
|
52
|
+
*
|
|
53
|
+
* End-of-node (offset === length) naturally becomes an append —
|
|
54
|
+
* sliceNode produces no right half, incoming merges with left or gets pushed.
|
|
55
|
+
* No separate append function needed.
|
|
56
|
+
*/
|
|
57
|
+
declare function insertAt<T extends BlockType>(block: AnyBlock, nodeIndex: number, offset: number, incoming: Node): Result<BlockContent<T>>;
|
|
58
|
+
declare function deleteLastChar(block: AnyBlock): Result<BlockContent<BlockType>>;
|
|
59
|
+
/**
|
|
60
|
+
* Delete content across a selection.
|
|
61
|
+
* After deletion, left and right boundaries are merged if formats match.
|
|
62
|
+
*
|
|
63
|
+
* For code/equation blocks: deletes within the single tuple node's text/latex.
|
|
64
|
+
*/
|
|
65
|
+
declare function deleteRange<T extends BlockType>(block: AnyBlock, startNodeIndex: number, startOffset: number, endNodeIndex: number, endOffset: number): Result<BlockContent<T>>;
|
|
66
|
+
/**
|
|
67
|
+
* Split a block at a given position into two blocks.
|
|
68
|
+
* The original block keeps content before the cursor.
|
|
69
|
+
* A new block gets content after the cursor — always of type "paragraph".
|
|
70
|
+
*
|
|
71
|
+
* Not supported for code/equation blocks — returns Err.
|
|
72
|
+
*/
|
|
73
|
+
declare function splitBlock(block: AnyBlock, nodeIndex: number, offset: number): Result<[AnyBlock, AnyBlock]>;
|
|
74
|
+
/**
|
|
75
|
+
* Merge blockB into blockA — blockB's content is appended to blockA's content.
|
|
76
|
+
* blockA's type and meta are preserved.
|
|
77
|
+
*
|
|
78
|
+
* Not supported if either block is code or equation — returns Err.
|
|
79
|
+
*/
|
|
80
|
+
declare function mergeBlocks(blockA: AnyBlock, blockB: AnyBlock): Result<AnyBlock>;
|
|
81
|
+
/**
|
|
82
|
+
* Replace a selected range with an incoming Node — atomic deleteRange + insertAt.
|
|
83
|
+
* This is what fires when the user has a selection and types a character.
|
|
84
|
+
*
|
|
85
|
+
* Internally chains:
|
|
86
|
+
* 1. deleteRange — remove selected content
|
|
87
|
+
* 2. insertAt — insert incoming at the start of the deleted range
|
|
88
|
+
*/
|
|
89
|
+
declare function replaceRange<T extends BlockType>(block: AnyBlock, startNodeIndex: number, startOffset: number, endNodeIndex: number, endOffset: number, incoming: Node): Result<BlockContent<T>>;
|
|
90
|
+
|
|
91
|
+
type TextNode = Extract<Node, {
|
|
92
|
+
type: "text";
|
|
93
|
+
}>;
|
|
94
|
+
type TextFormat = Pick<TextNode, "bold" | "italic" | "underline" | "strikethrough" | "highlighted" | "color" | "link">;
|
|
95
|
+
type NodeSelection = {
|
|
96
|
+
startIndex: number;
|
|
97
|
+
startOffset: number;
|
|
98
|
+
endIndex: number;
|
|
99
|
+
endOffset: number;
|
|
100
|
+
};
|
|
101
|
+
declare function formatNodes(nodes: Node[], sel: NodeSelection, format: keyof TextFormat, value?: unknown): Result<Node[]>;
|
|
102
|
+
declare function mergeAdjacentNodes(nodes: Node[]): Node[];
|
|
103
|
+
declare const toggleBold: (nodes: Node[], sel: NodeSelection) => Result<Node[], string>;
|
|
104
|
+
declare const toggleItalic: (nodes: Node[], sel: NodeSelection) => Result<Node[], string>;
|
|
105
|
+
declare const toggleUnderline: (nodes: Node[], sel: NodeSelection) => Result<Node[], string>;
|
|
106
|
+
declare const toggleStrikethrough: (nodes: Node[], sel: NodeSelection) => Result<Node[], string>;
|
|
107
|
+
declare const toggleHighlight: (nodes: Node[], sel: NodeSelection, color?: "yellow" | "green") => Result<Node[], string>;
|
|
108
|
+
declare const toggleColor: (nodes: Node[], sel: NodeSelection, color: "red" | "blue" | "green") => Result<Node[], string>;
|
|
109
|
+
declare const setLink: (nodes: Node[], sel: NodeSelection, href: string) => Result<Node[], string>;
|
|
110
|
+
declare const removeLink: (nodes: Node[], sel: NodeSelection) => Result<Node[], string>;
|
|
111
|
+
|
|
112
|
+
type TransformResult = {
|
|
113
|
+
block: AnyBlock;
|
|
114
|
+
converted: boolean;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Call this when a space is typed.
|
|
118
|
+
*
|
|
119
|
+
* Guards (returns converted=false if any fail):
|
|
120
|
+
* 1. block.type must be "paragraph"
|
|
121
|
+
* 2. cursorOffset must be ≤ trigger.length + 1
|
|
122
|
+
* — ensures the space was typed right after the trigger at position 0
|
|
123
|
+
* — blocks mid-text spaces like "hello - " from converting
|
|
124
|
+
* 3. text must start with a known trigger prefix
|
|
125
|
+
*/
|
|
126
|
+
declare function applyMarkdownTransform(block: AnyBlock, cursorOffset: number): Result<TransformResult>;
|
|
127
|
+
/**
|
|
128
|
+
* Convert a block to a new type while preserving content as much as possible.
|
|
129
|
+
*
|
|
130
|
+
* rich → rich keep content as-is, update type + meta
|
|
131
|
+
* rich → code strip all formatting, concat all text → [CodeNode]
|
|
132
|
+
* rich → equation strip all formatting, concat all text → [EquationNode]
|
|
133
|
+
* code → rich single TextNode with code.text
|
|
134
|
+
* equation → rich single TextNode with equation.latex
|
|
135
|
+
* code → equation [EquationNode] with code.text as latex
|
|
136
|
+
* equation → code [CodeNode] with equation.latex as text
|
|
137
|
+
*/
|
|
138
|
+
declare function changeBlockType<T extends BlockType>(block: AnyBlock, targetType: T): Result<Block<T>>;
|
|
139
|
+
/**
|
|
140
|
+
* Toggle the checked state of a todo block.
|
|
141
|
+
* checked: undefined → checked: true → checked: undefined (cycle)
|
|
142
|
+
*/
|
|
143
|
+
declare function toggleTodo(block: AnyBlock): Result<Block<"todo">>;
|
|
144
|
+
type IndentableBlock = Block<"bullet"> | Block<"number"> | Block<"todo">;
|
|
145
|
+
/**
|
|
146
|
+
* Increase the depth of a bullet, number, or todo block by 1.
|
|
147
|
+
* Max depth is 6. Returns Err if block type is not indentable.
|
|
148
|
+
*/
|
|
149
|
+
declare function indentBlock(block: AnyBlock): Result<IndentableBlock>;
|
|
150
|
+
/**
|
|
151
|
+
* Decrease the depth of a bullet, number, or todo block by 1.
|
|
152
|
+
* Min depth is 0. Returns Err if block type is not indentable or already at 0.
|
|
153
|
+
*/
|
|
154
|
+
declare function outdentBlock(block: AnyBlock): Result<IndentableBlock>;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Serialize an array of blocks to a JSON string.
|
|
158
|
+
* Wraps JSON.stringify in Result.try so any circular ref or
|
|
159
|
+
* other stringify error surfaces as Err rather than throwing.
|
|
160
|
+
*/
|
|
161
|
+
declare function serialize(blocks: AnyBlock[]): Result<string, unknown>;
|
|
162
|
+
/**
|
|
163
|
+
* Deserialize a JSON string back into AnyBlock[].
|
|
164
|
+
* Validates:
|
|
165
|
+
* - valid JSON
|
|
166
|
+
* - top level is an array
|
|
167
|
+
* - each item has id (string), type (known BlockType), meta (object), content (array)
|
|
168
|
+
*/
|
|
169
|
+
declare function deserialize(json: string): Result<AnyBlock[], string>;
|
|
170
|
+
/**
|
|
171
|
+
* Serialize a Node[] to a JSON string — for clipboard copy.
|
|
172
|
+
* Preserves all formatting.
|
|
173
|
+
*/
|
|
174
|
+
declare function serializeNodes(nodes: Node[]): Result<string, unknown>;
|
|
175
|
+
/**
|
|
176
|
+
* Deserialize a JSON string back into Node[] — for clipboard paste.
|
|
177
|
+
* Validates each node shape before returning.
|
|
178
|
+
*/
|
|
179
|
+
declare function deserializeNodes(json: string): Result<Node[], string>;
|
|
180
|
+
/**
|
|
181
|
+
* Extract plain text from a Node[] — strips all formatting.
|
|
182
|
+
* text → text
|
|
183
|
+
* code → text
|
|
184
|
+
* equation → latex
|
|
185
|
+
*/
|
|
186
|
+
declare function toPlainText(nodes: Node[]): string;
|
|
187
|
+
/**
|
|
188
|
+
* Convert an array of blocks to a markdown string.
|
|
189
|
+
*
|
|
190
|
+
* paragraph → plain inline
|
|
191
|
+
* heading1-3 → # / ## / ###
|
|
192
|
+
* bullet → - (indented by depth)
|
|
193
|
+
* number → 1. (indented by depth)
|
|
194
|
+
* todo → - [ ] / - [x] (indented by depth)
|
|
195
|
+
* code → ```language\n...\n```
|
|
196
|
+
* equation → $$...$$ (block equation)
|
|
197
|
+
*/
|
|
198
|
+
declare function toMarkdown(blocks: AnyBlock[]): string;
|
|
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
|
+
type HistoryEntry = {
|
|
236
|
+
blocks: AnyBlock[];
|
|
237
|
+
timestamp: number;
|
|
238
|
+
};
|
|
239
|
+
type History = {
|
|
240
|
+
past: HistoryEntry[];
|
|
241
|
+
present: HistoryEntry;
|
|
242
|
+
future: HistoryEntry[];
|
|
243
|
+
};
|
|
244
|
+
/**
|
|
245
|
+
* Create a new history instance with the given initial blocks.
|
|
246
|
+
*/
|
|
247
|
+
declare function createHistory(initialBlocks: AnyBlock[]): History;
|
|
248
|
+
/**
|
|
249
|
+
* Push a new state onto the history stack.
|
|
250
|
+
* Clears the future — same as any editor after a new action post-undo.
|
|
251
|
+
* Optionally cap the max history size to avoid unbounded memory growth.
|
|
252
|
+
*/
|
|
253
|
+
declare function push(history: History, blocks: AnyBlock[], maxSize?: number): History;
|
|
254
|
+
/**
|
|
255
|
+
* Move back one step in history.
|
|
256
|
+
* Returns Err if there is nothing to undo.
|
|
257
|
+
*/
|
|
258
|
+
declare function undo(history: History): Result<History, string>;
|
|
259
|
+
/**
|
|
260
|
+
* Move forward one step in history.
|
|
261
|
+
* Returns Err if there is nothing to redo.
|
|
262
|
+
*/
|
|
263
|
+
declare function redo(history: History): Result<History, string>;
|
|
264
|
+
declare const canUndo: (history: History) => boolean;
|
|
265
|
+
declare const canRedo: (history: History) => boolean;
|
|
266
|
+
declare const currentBlocks: (history: History) => AnyBlock[];
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Generate a unique block id.
|
|
270
|
+
* Pass a custom fn to override the default (uuid v7).
|
|
271
|
+
*/
|
|
272
|
+
declare function generateId(fn?: () => string): string;
|
|
273
|
+
/**
|
|
274
|
+
* Create a new block of the given type with default content and meta.
|
|
275
|
+
* Optionally pass a custom id generator.
|
|
276
|
+
*
|
|
277
|
+
* @param type - the block type to create
|
|
278
|
+
* @param idFn - optional custom id generator (defaults to uuid v7)
|
|
279
|
+
*/
|
|
280
|
+
declare function createBlock<T extends BlockType>(type: T, idFn?: () => string): Result<Block<T>, unknown>;
|
|
281
|
+
declare function insertBlockAfter<T extends BlockType>(blocks: AnyBlock[], afterId: string, type: T, idFn?: () => string): Result<{
|
|
282
|
+
blocks: AnyBlock[];
|
|
283
|
+
newId: string;
|
|
284
|
+
}, unknown>;
|
|
285
|
+
declare function deleteBlock(blocks: AnyBlock[], id: string): {
|
|
286
|
+
blocks: AnyBlock[];
|
|
287
|
+
prevId: string;
|
|
288
|
+
};
|
|
289
|
+
declare function duplicateBlock(incoming: AnyBlock, newId: string): AnyBlock;
|
|
290
|
+
/**
|
|
291
|
+
* Move a block up or down by one position in the array.
|
|
292
|
+
* Returns the same array unchanged if block is already at the boundary.
|
|
293
|
+
* Returns Err if id not found.
|
|
294
|
+
*/
|
|
295
|
+
declare function moveBlock(blocks: AnyBlock[], id: string, direction: "up" | "down"): Result<AnyBlock[], string>;
|
|
296
|
+
|
|
297
|
+
/**─── Wrappers ──────────────────────────────────────────────────────────────────
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,306 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import { Result } from '@reiwuzen/result';
|
|
2
|
+
|
|
3
|
+
type BlockType = "paragraph" | "heading1" | "heading2" | "heading3" | "bullet" | "number" | "todo" | "equation" | "code";
|
|
4
|
+
type Node = {
|
|
5
|
+
type: "text";
|
|
6
|
+
text: string;
|
|
7
|
+
highlighted?: "yellow" | "green";
|
|
8
|
+
color?: "red" | 'blue' | 'green';
|
|
9
|
+
bold?: true;
|
|
10
|
+
italic?: true;
|
|
11
|
+
underline?: true;
|
|
12
|
+
strikethrough?: true;
|
|
13
|
+
link?: string;
|
|
14
|
+
} | {
|
|
15
|
+
type: "code";
|
|
16
|
+
text: string;
|
|
17
|
+
} | {
|
|
18
|
+
type: "equation";
|
|
19
|
+
latex: string;
|
|
20
|
+
};
|
|
21
|
+
type BlockMeta<T extends BlockType> = T extends "todo" ? {
|
|
22
|
+
checked?: true;
|
|
23
|
+
depth: number;
|
|
24
|
+
} : T extends "bullet" | "number" ? {
|
|
25
|
+
depth: number;
|
|
26
|
+
} : T extends "code" ? {
|
|
27
|
+
language?: string;
|
|
28
|
+
} : Record<string, never>;
|
|
29
|
+
type BlockContent<T extends BlockType> = T extends "code" ? [Extract<Node, {
|
|
30
|
+
type: "code";
|
|
31
|
+
}>] : T extends "equation" ? [Extract<Node, {
|
|
32
|
+
type: "equation";
|
|
33
|
+
}>] : Node[];
|
|
34
|
+
type Block<T extends BlockType = BlockType> = {
|
|
35
|
+
id: string;
|
|
36
|
+
type: T;
|
|
37
|
+
meta: BlockMeta<T>;
|
|
38
|
+
content: BlockContent<T>;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Discriminated Union of Block
|
|
42
|
+
*/
|
|
43
|
+
type AnyBlock = {
|
|
44
|
+
[K in BlockType]: Block<K>;
|
|
45
|
+
}[BlockType];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Insert a Node at a specific position within block content.
|
|
49
|
+
*
|
|
50
|
+
* - nodeIndex: which node to insert into
|
|
51
|
+
* - offset: char position within that node
|
|
52
|
+
*
|
|
53
|
+
* End-of-node (offset === length) naturally becomes an append —
|
|
54
|
+
* sliceNode produces no right half, incoming merges with left or gets pushed.
|
|
55
|
+
* No separate append function needed.
|
|
56
|
+
*/
|
|
57
|
+
declare function insertAt<T extends BlockType>(block: AnyBlock, nodeIndex: number, offset: number, incoming: Node): Result<BlockContent<T>>;
|
|
58
|
+
declare function deleteLastChar(block: AnyBlock): Result<BlockContent<BlockType>>;
|
|
59
|
+
/**
|
|
60
|
+
* Delete content across a selection.
|
|
61
|
+
* After deletion, left and right boundaries are merged if formats match.
|
|
62
|
+
*
|
|
63
|
+
* For code/equation blocks: deletes within the single tuple node's text/latex.
|
|
64
|
+
*/
|
|
65
|
+
declare function deleteRange<T extends BlockType>(block: AnyBlock, startNodeIndex: number, startOffset: number, endNodeIndex: number, endOffset: number): Result<BlockContent<T>>;
|
|
66
|
+
/**
|
|
67
|
+
* Split a block at a given position into two blocks.
|
|
68
|
+
* The original block keeps content before the cursor.
|
|
69
|
+
* A new block gets content after the cursor — always of type "paragraph".
|
|
70
|
+
*
|
|
71
|
+
* Not supported for code/equation blocks — returns Err.
|
|
72
|
+
*/
|
|
73
|
+
declare function splitBlock(block: AnyBlock, nodeIndex: number, offset: number): Result<[AnyBlock, AnyBlock]>;
|
|
74
|
+
/**
|
|
75
|
+
* Merge blockB into blockA — blockB's content is appended to blockA's content.
|
|
76
|
+
* blockA's type and meta are preserved.
|
|
77
|
+
*
|
|
78
|
+
* Not supported if either block is code or equation — returns Err.
|
|
79
|
+
*/
|
|
80
|
+
declare function mergeBlocks(blockA: AnyBlock, blockB: AnyBlock): Result<AnyBlock>;
|
|
81
|
+
/**
|
|
82
|
+
* Replace a selected range with an incoming Node — atomic deleteRange + insertAt.
|
|
83
|
+
* This is what fires when the user has a selection and types a character.
|
|
84
|
+
*
|
|
85
|
+
* Internally chains:
|
|
86
|
+
* 1. deleteRange — remove selected content
|
|
87
|
+
* 2. insertAt — insert incoming at the start of the deleted range
|
|
88
|
+
*/
|
|
89
|
+
declare function replaceRange<T extends BlockType>(block: AnyBlock, startNodeIndex: number, startOffset: number, endNodeIndex: number, endOffset: number, incoming: Node): Result<BlockContent<T>>;
|
|
90
|
+
|
|
91
|
+
type TextNode = Extract<Node, {
|
|
92
|
+
type: "text";
|
|
93
|
+
}>;
|
|
94
|
+
type TextFormat = Pick<TextNode, "bold" | "italic" | "underline" | "strikethrough" | "highlighted" | "color" | "link">;
|
|
95
|
+
type NodeSelection = {
|
|
96
|
+
startIndex: number;
|
|
97
|
+
startOffset: number;
|
|
98
|
+
endIndex: number;
|
|
99
|
+
endOffset: number;
|
|
100
|
+
};
|
|
101
|
+
declare function formatNodes(nodes: Node[], sel: NodeSelection, format: keyof TextFormat, value?: unknown): Result<Node[]>;
|
|
102
|
+
declare function mergeAdjacentNodes(nodes: Node[]): Node[];
|
|
103
|
+
declare const toggleBold: (nodes: Node[], sel: NodeSelection) => Result<Node[], string>;
|
|
104
|
+
declare const toggleItalic: (nodes: Node[], sel: NodeSelection) => Result<Node[], string>;
|
|
105
|
+
declare const toggleUnderline: (nodes: Node[], sel: NodeSelection) => Result<Node[], string>;
|
|
106
|
+
declare const toggleStrikethrough: (nodes: Node[], sel: NodeSelection) => Result<Node[], string>;
|
|
107
|
+
declare const toggleHighlight: (nodes: Node[], sel: NodeSelection, color?: "yellow" | "green") => Result<Node[], string>;
|
|
108
|
+
declare const toggleColor: (nodes: Node[], sel: NodeSelection, color: "red" | "blue" | "green") => Result<Node[], string>;
|
|
109
|
+
declare const setLink: (nodes: Node[], sel: NodeSelection, href: string) => Result<Node[], string>;
|
|
110
|
+
declare const removeLink: (nodes: Node[], sel: NodeSelection) => Result<Node[], string>;
|
|
111
|
+
|
|
112
|
+
type TransformResult = {
|
|
113
|
+
block: AnyBlock;
|
|
114
|
+
converted: boolean;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Call this when a space is typed.
|
|
118
|
+
*
|
|
119
|
+
* Guards (returns converted=false if any fail):
|
|
120
|
+
* 1. block.type must be "paragraph"
|
|
121
|
+
* 2. cursorOffset must be ≤ trigger.length + 1
|
|
122
|
+
* — ensures the space was typed right after the trigger at position 0
|
|
123
|
+
* — blocks mid-text spaces like "hello - " from converting
|
|
124
|
+
* 3. text must start with a known trigger prefix
|
|
125
|
+
*/
|
|
126
|
+
declare function applyMarkdownTransform(block: AnyBlock, cursorOffset: number): Result<TransformResult>;
|
|
127
|
+
/**
|
|
128
|
+
* Convert a block to a new type while preserving content as much as possible.
|
|
129
|
+
*
|
|
130
|
+
* rich → rich keep content as-is, update type + meta
|
|
131
|
+
* rich → code strip all formatting, concat all text → [CodeNode]
|
|
132
|
+
* rich → equation strip all formatting, concat all text → [EquationNode]
|
|
133
|
+
* code → rich single TextNode with code.text
|
|
134
|
+
* equation → rich single TextNode with equation.latex
|
|
135
|
+
* code → equation [EquationNode] with code.text as latex
|
|
136
|
+
* equation → code [CodeNode] with equation.latex as text
|
|
137
|
+
*/
|
|
138
|
+
declare function changeBlockType<T extends BlockType>(block: AnyBlock, targetType: T): Result<Block<T>>;
|
|
139
|
+
/**
|
|
140
|
+
* Toggle the checked state of a todo block.
|
|
141
|
+
* checked: undefined → checked: true → checked: undefined (cycle)
|
|
142
|
+
*/
|
|
143
|
+
declare function toggleTodo(block: AnyBlock): Result<Block<"todo">>;
|
|
144
|
+
type IndentableBlock = Block<"bullet"> | Block<"number"> | Block<"todo">;
|
|
145
|
+
/**
|
|
146
|
+
* Increase the depth of a bullet, number, or todo block by 1.
|
|
147
|
+
* Max depth is 6. Returns Err if block type is not indentable.
|
|
148
|
+
*/
|
|
149
|
+
declare function indentBlock(block: AnyBlock): Result<IndentableBlock>;
|
|
150
|
+
/**
|
|
151
|
+
* Decrease the depth of a bullet, number, or todo block by 1.
|
|
152
|
+
* Min depth is 0. Returns Err if block type is not indentable or already at 0.
|
|
153
|
+
*/
|
|
154
|
+
declare function outdentBlock(block: AnyBlock): Result<IndentableBlock>;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Serialize an array of blocks to a JSON string.
|
|
158
|
+
* Wraps JSON.stringify in Result.try so any circular ref or
|
|
159
|
+
* other stringify error surfaces as Err rather than throwing.
|
|
160
|
+
*/
|
|
161
|
+
declare function serialize(blocks: AnyBlock[]): Result<string, unknown>;
|
|
162
|
+
/**
|
|
163
|
+
* Deserialize a JSON string back into AnyBlock[].
|
|
164
|
+
* Validates:
|
|
165
|
+
* - valid JSON
|
|
166
|
+
* - top level is an array
|
|
167
|
+
* - each item has id (string), type (known BlockType), meta (object), content (array)
|
|
168
|
+
*/
|
|
169
|
+
declare function deserialize(json: string): Result<AnyBlock[], string>;
|
|
170
|
+
/**
|
|
171
|
+
* Serialize a Node[] to a JSON string — for clipboard copy.
|
|
172
|
+
* Preserves all formatting.
|
|
173
|
+
*/
|
|
174
|
+
declare function serializeNodes(nodes: Node[]): Result<string, unknown>;
|
|
175
|
+
/**
|
|
176
|
+
* Deserialize a JSON string back into Node[] — for clipboard paste.
|
|
177
|
+
* Validates each node shape before returning.
|
|
178
|
+
*/
|
|
179
|
+
declare function deserializeNodes(json: string): Result<Node[], string>;
|
|
180
|
+
/**
|
|
181
|
+
* Extract plain text from a Node[] — strips all formatting.
|
|
182
|
+
* text → text
|
|
183
|
+
* code → text
|
|
184
|
+
* equation → latex
|
|
185
|
+
*/
|
|
186
|
+
declare function toPlainText(nodes: Node[]): string;
|
|
187
|
+
/**
|
|
188
|
+
* Convert an array of blocks to a markdown string.
|
|
189
|
+
*
|
|
190
|
+
* paragraph → plain inline
|
|
191
|
+
* heading1-3 → # / ## / ###
|
|
192
|
+
* bullet → - (indented by depth)
|
|
193
|
+
* number → 1. (indented by depth)
|
|
194
|
+
* todo → - [ ] / - [x] (indented by depth)
|
|
195
|
+
* code → ```language\n...\n```
|
|
196
|
+
* equation → $$...$$ (block equation)
|
|
197
|
+
*/
|
|
198
|
+
declare function toMarkdown(blocks: AnyBlock[]): string;
|
|
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
|
+
type HistoryEntry = {
|
|
236
|
+
blocks: AnyBlock[];
|
|
237
|
+
timestamp: number;
|
|
238
|
+
};
|
|
239
|
+
type History = {
|
|
240
|
+
past: HistoryEntry[];
|
|
241
|
+
present: HistoryEntry;
|
|
242
|
+
future: HistoryEntry[];
|
|
243
|
+
};
|
|
244
|
+
/**
|
|
245
|
+
* Create a new history instance with the given initial blocks.
|
|
246
|
+
*/
|
|
247
|
+
declare function createHistory(initialBlocks: AnyBlock[]): History;
|
|
248
|
+
/**
|
|
249
|
+
* Push a new state onto the history stack.
|
|
250
|
+
* Clears the future — same as any editor after a new action post-undo.
|
|
251
|
+
* Optionally cap the max history size to avoid unbounded memory growth.
|
|
252
|
+
*/
|
|
253
|
+
declare function push(history: History, blocks: AnyBlock[], maxSize?: number): History;
|
|
254
|
+
/**
|
|
255
|
+
* Move back one step in history.
|
|
256
|
+
* Returns Err if there is nothing to undo.
|
|
257
|
+
*/
|
|
258
|
+
declare function undo(history: History): Result<History, string>;
|
|
259
|
+
/**
|
|
260
|
+
* Move forward one step in history.
|
|
261
|
+
* Returns Err if there is nothing to redo.
|
|
262
|
+
*/
|
|
263
|
+
declare function redo(history: History): Result<History, string>;
|
|
264
|
+
declare const canUndo: (history: History) => boolean;
|
|
265
|
+
declare const canRedo: (history: History) => boolean;
|
|
266
|
+
declare const currentBlocks: (history: History) => AnyBlock[];
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Generate a unique block id.
|
|
270
|
+
* Pass a custom fn to override the default (uuid v7).
|
|
271
|
+
*/
|
|
272
|
+
declare function generateId(fn?: () => string): string;
|
|
273
|
+
/**
|
|
274
|
+
* Create a new block of the given type with default content and meta.
|
|
275
|
+
* Optionally pass a custom id generator.
|
|
276
|
+
*
|
|
277
|
+
* @param type - the block type to create
|
|
278
|
+
* @param idFn - optional custom id generator (defaults to uuid v7)
|
|
279
|
+
*/
|
|
280
|
+
declare function createBlock<T extends BlockType>(type: T, idFn?: () => string): Result<Block<T>, unknown>;
|
|
281
|
+
declare function insertBlockAfter<T extends BlockType>(blocks: AnyBlock[], afterId: string, type: T, idFn?: () => string): Result<{
|
|
282
|
+
blocks: AnyBlock[];
|
|
283
|
+
newId: string;
|
|
284
|
+
}, unknown>;
|
|
285
|
+
declare function deleteBlock(blocks: AnyBlock[], id: string): {
|
|
286
|
+
blocks: AnyBlock[];
|
|
287
|
+
prevId: string;
|
|
288
|
+
};
|
|
289
|
+
declare function duplicateBlock(incoming: AnyBlock, newId: string): AnyBlock;
|
|
290
|
+
/**
|
|
291
|
+
* Move a block up or down by one position in the array.
|
|
292
|
+
* Returns the same array unchanged if block is already at the boundary.
|
|
293
|
+
* Returns Err if id not found.
|
|
294
|
+
*/
|
|
295
|
+
declare function moveBlock(blocks: AnyBlock[], id: string, direction: "up" | "down"): Result<AnyBlock[], string>;
|
|
296
|
+
|
|
297
|
+
/**─── Wrappers ──────────────────────────────────────────────────────────────────
|
|
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 };
|