@rtif-sdk/core 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/LICENSE +21 -0
- package/README.md +70 -0
- package/dist/apply.d.ts +31 -0
- package/dist/apply.d.ts.map +1 -0
- package/dist/apply.js +604 -0
- package/dist/apply.js.map +1 -0
- package/dist/error.d.ts +19 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +21 -0
- package/dist/error.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/model.d.ts +58 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +6 -0
- package/dist/model.js.map +1 -0
- package/dist/normalize.d.ts +40 -0
- package/dist/normalize.d.ts.map +1 -0
- package/dist/normalize.js +74 -0
- package/dist/normalize.js.map +1 -0
- package/dist/operations.d.ts +90 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +6 -0
- package/dist/operations.js.map +1 -0
- package/dist/queries.d.ts +68 -0
- package/dist/queries.d.ts.map +1 -0
- package/dist/queries.js +159 -0
- package/dist/queries.js.map +1 -0
- package/dist/resolve.d.ts +58 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +90 -0
- package/dist/resolve.js.map +1 -0
- package/dist/serialization.d.ts +89 -0
- package/dist/serialization.d.ts.map +1 -0
- package/dist/serialization.js +39 -0
- package/dist/serialization.js.map +1 -0
- package/dist/validate.d.ts +38 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +93 -0
- package/dist/validate.js.map +1 -0
- package/package.json +25 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH;;;GAGG;AACH,MAAM,OAAO,SAAU,SAAQ,KAAK;IACzB,IAAI,CAAgB;IAE7B,YAAY,IAAmB,EAAE,OAAe;QAC9C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type { Document, Block, Span, Position, Selection } from './model.js';
|
|
2
|
+
export { RtifError } from './error.js';
|
|
3
|
+
export type { RtifErrorCode } from './error.js';
|
|
4
|
+
export type { Operation, InsertText, DeleteText, SplitBlock, MergeBlock, SetBlockAttrs, SetBlockType, SetSpanMarks, SetMeta, } from './operations.js';
|
|
5
|
+
export { apply } from './apply.js';
|
|
6
|
+
export type { ApplyResult } from './apply.js';
|
|
7
|
+
export { normalizeSpans, marksEqual } from './normalize.js';
|
|
8
|
+
export { resolve, blockTextLength, docLength } from './resolve.js';
|
|
9
|
+
export type { ResolvedPosition } from './resolve.js';
|
|
10
|
+
export { validate } from './validate.js';
|
|
11
|
+
export type { ValidationResult, ValidationError } from './validate.js';
|
|
12
|
+
export { getMarksAtOffset, getMarksInRange, getBlockAtOffset } from './queries.js';
|
|
13
|
+
export type { RangeMarksResult } from './queries.js';
|
|
14
|
+
export { createMarkSerializerRegistry } from './serialization.js';
|
|
15
|
+
export type { MarkSerializer, MarkSerializerRegistry, SerializationFormat, } from './serialization.js';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG7E,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGhD,YAAY,EACV,SAAS,EACT,UAAU,EACV,UAAU,EACV,UAAU,EACV,UAAU,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,OAAO,GACR,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5D,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACnE,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEvE,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACnF,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AAClE,YAAY,EACV,cAAc,EACd,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Error types
|
|
2
|
+
export { RtifError } from './error.js';
|
|
3
|
+
// Core functions
|
|
4
|
+
export { apply } from './apply.js';
|
|
5
|
+
export { normalizeSpans, marksEqual } from './normalize.js';
|
|
6
|
+
export { resolve, blockTextLength, docLength } from './resolve.js';
|
|
7
|
+
export { validate } from './validate.js';
|
|
8
|
+
export { getMarksAtOffset, getMarksInRange, getBlockAtOffset } from './queries.js';
|
|
9
|
+
// Serialization contracts
|
|
10
|
+
export { createMarkSerializerRegistry } from './serialization.js';
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc;AACd,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAgBvC,iBAAiB;AACjB,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5D,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGnE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGnF,0BAA0B;AAC1B,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core RTIF data model types.
|
|
3
|
+
* See SPEC.md §2 for full specification.
|
|
4
|
+
*/
|
|
5
|
+
/** Root document container */
|
|
6
|
+
export interface Document {
|
|
7
|
+
/** Spec version for forward compatibility */
|
|
8
|
+
version: 1;
|
|
9
|
+
/** Ordered list of content blocks */
|
|
10
|
+
blocks: Block[];
|
|
11
|
+
/**
|
|
12
|
+
* Global metadata (optional).
|
|
13
|
+
* Plugins may store data here (e.g., document-level settings).
|
|
14
|
+
* Core ignores unknown keys.
|
|
15
|
+
*/
|
|
16
|
+
meta?: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
/** A line-level container (paragraph, heading, list item, etc.) */
|
|
19
|
+
export interface Block {
|
|
20
|
+
/** Unique ID, generated once, stable across edits */
|
|
21
|
+
id: string;
|
|
22
|
+
/**
|
|
23
|
+
* Block type. Core only knows "text".
|
|
24
|
+
* Plugins register additional types via the BlockType registry.
|
|
25
|
+
*/
|
|
26
|
+
type: string;
|
|
27
|
+
/** Inline content within the block */
|
|
28
|
+
spans: Span[];
|
|
29
|
+
/**
|
|
30
|
+
* Block-level attributes (optional).
|
|
31
|
+
* Plugins use this for heading level, list depth, indentation, etc.
|
|
32
|
+
*/
|
|
33
|
+
attrs?: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
/** A contiguous run of characters sharing the same set of format marks */
|
|
36
|
+
export interface Span {
|
|
37
|
+
/** The raw text content */
|
|
38
|
+
text: string;
|
|
39
|
+
/**
|
|
40
|
+
* Set of active format marks on this span.
|
|
41
|
+
* Core defines no built-in marks — they are all plugin-provided.
|
|
42
|
+
* An empty object (or omitted) means plain, unformatted text.
|
|
43
|
+
*/
|
|
44
|
+
marks?: Record<string, unknown>;
|
|
45
|
+
}
|
|
46
|
+
/** A single point in the document (absolute character offset, 0-indexed) */
|
|
47
|
+
export interface Position {
|
|
48
|
+
offset: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A selection defined by anchor (where the user started) and focus (cursor).
|
|
52
|
+
* When anchor.offset === focus.offset, this is a collapsed cursor (caret).
|
|
53
|
+
*/
|
|
54
|
+
export interface Selection {
|
|
55
|
+
anchor: Position;
|
|
56
|
+
focus: Position;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,8BAA8B;AAC9B,MAAM,WAAW,QAAQ;IACvB,6CAA6C;IAC7C,OAAO,EAAE,CAAC,CAAC;IAEX,qCAAqC;IACrC,MAAM,EAAE,KAAK,EAAE,CAAC;IAEhB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,mEAAmE;AACnE,MAAM,WAAW,KAAK;IACpB,qDAAqD;IACrD,EAAE,EAAE,MAAM,CAAC;IAEX;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb,sCAAsC;IACtC,KAAK,EAAE,IAAI,EAAE,CAAC;IAEd;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,0EAA0E;AAC1E,MAAM,WAAW,IAAI;IACnB,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IAEb;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,4EAA4E;AAC5E,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,QAAQ,CAAC;IACjB,KAAK,EAAE,QAAQ,CAAC;CACjB"}
|
package/dist/model.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Span normalization — ensures documents are always in canonical form.
|
|
3
|
+
* See SPEC.md §3.3 for specification.
|
|
4
|
+
*/
|
|
5
|
+
import type { Span } from './model.js';
|
|
6
|
+
/**
|
|
7
|
+
* Check if two mark objects are deeply equal.
|
|
8
|
+
* Treats `undefined` and `{}` as equivalent (both mean "no marks").
|
|
9
|
+
*
|
|
10
|
+
* @param a - First marks object
|
|
11
|
+
* @param b - Second marks object
|
|
12
|
+
* @returns true if marks are semantically equal
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* marksEqual({ bold: true }, { bold: true }); // => true
|
|
17
|
+
* marksEqual(undefined, {}); // => true
|
|
18
|
+
* marksEqual({ bold: true }, { italic: true }); // => false
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function marksEqual(a: Record<string, unknown> | undefined, b: Record<string, unknown> | undefined): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Normalize an array of spans:
|
|
24
|
+
* 1. Adjacent spans with identical marks are merged into one.
|
|
25
|
+
* 2. Empty spans (text === "") are removed.
|
|
26
|
+
* 3. If the result is empty, returns one span: `{ text: "" }`.
|
|
27
|
+
*
|
|
28
|
+
* This guarantees two semantically identical documents have identical JSON.
|
|
29
|
+
*
|
|
30
|
+
* @param spans - The spans to normalize
|
|
31
|
+
* @returns A new normalized array of spans
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* normalizeSpans([{ text: 'a' }, { text: 'b' }]);
|
|
36
|
+
* // => [{ text: 'ab' }]
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function normalizeSpans(spans: ReadonlyArray<Span>): Span[];
|
|
40
|
+
//# sourceMappingURL=normalize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../src/normalize.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CACxB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EACtC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GACrC,OAAO,CAKT;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CA+BjE"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Span normalization — ensures documents are always in canonical form.
|
|
3
|
+
* See SPEC.md §3.3 for specification.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Check if two mark objects are deeply equal.
|
|
7
|
+
* Treats `undefined` and `{}` as equivalent (both mean "no marks").
|
|
8
|
+
*
|
|
9
|
+
* @param a - First marks object
|
|
10
|
+
* @param b - Second marks object
|
|
11
|
+
* @returns true if marks are semantically equal
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* marksEqual({ bold: true }, { bold: true }); // => true
|
|
16
|
+
* marksEqual(undefined, {}); // => true
|
|
17
|
+
* marksEqual({ bold: true }, { italic: true }); // => false
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function marksEqual(a, b) {
|
|
21
|
+
const aKeys = Object.keys(a ?? {});
|
|
22
|
+
const bKeys = Object.keys(b ?? {});
|
|
23
|
+
if (aKeys.length !== bKeys.length)
|
|
24
|
+
return false;
|
|
25
|
+
return aKeys.every((key) => (a ?? {})[key] === (b ?? {})[key]);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Normalize an array of spans:
|
|
29
|
+
* 1. Adjacent spans with identical marks are merged into one.
|
|
30
|
+
* 2. Empty spans (text === "") are removed.
|
|
31
|
+
* 3. If the result is empty, returns one span: `{ text: "" }`.
|
|
32
|
+
*
|
|
33
|
+
* This guarantees two semantically identical documents have identical JSON.
|
|
34
|
+
*
|
|
35
|
+
* @param spans - The spans to normalize
|
|
36
|
+
* @returns A new normalized array of spans
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* normalizeSpans([{ text: 'a' }, { text: 'b' }]);
|
|
41
|
+
* // => [{ text: 'ab' }]
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export function normalizeSpans(spans) {
|
|
45
|
+
const result = [];
|
|
46
|
+
for (const span of spans) {
|
|
47
|
+
if (span.text === '')
|
|
48
|
+
continue;
|
|
49
|
+
const prev = result[result.length - 1];
|
|
50
|
+
if (prev && marksEqual(prev.marks, span.marks)) {
|
|
51
|
+
// Merge into previous span (create new object, don't mutate)
|
|
52
|
+
result[result.length - 1] = {
|
|
53
|
+
text: prev.text + span.text,
|
|
54
|
+
...(prev.marks && Object.keys(prev.marks).length > 0
|
|
55
|
+
? { marks: { ...prev.marks } }
|
|
56
|
+
: {}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// Copy span to avoid mutation
|
|
61
|
+
result.push({
|
|
62
|
+
text: span.text,
|
|
63
|
+
...(span.marks && Object.keys(span.marks).length > 0
|
|
64
|
+
? { marks: { ...span.marks } }
|
|
65
|
+
: {}),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (result.length === 0) {
|
|
70
|
+
return [{ text: '' }];
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=normalize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize.js","sourceRoot":"","sources":["../src/normalize.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,UAAU,CACxB,CAAsC,EACtC,CAAsC;IAEtC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACnC,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAChD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AACjE,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,cAAc,CAAC,KAA0B;IACvD,MAAM,MAAM,GAAW,EAAE,CAAC;IAE1B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,IAAI,KAAK,EAAE;YAAE,SAAS;QAE/B,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACvC,IAAI,IAAI,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/C,6DAA6D;YAC7D,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG;gBAC1B,IAAI,EAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI;gBAC3B,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC;oBAClD,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,EAAE;oBAC9B,CAAC,CAAC,EAAE,CAAC;aACR,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,8BAA8B;YAC9B,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC;oBAClD,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,EAAE;oBAC9B,CAAC,CAAC,EAAE,CAAC;aACR,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACxB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RTIF operation types — the complete set of document mutations.
|
|
3
|
+
* See SPEC.md §3.1 for full specification.
|
|
4
|
+
*/
|
|
5
|
+
/** Insert characters at a position */
|
|
6
|
+
export interface InsertText {
|
|
7
|
+
type: 'insert_text';
|
|
8
|
+
offset: number;
|
|
9
|
+
text: string;
|
|
10
|
+
}
|
|
11
|
+
/** Delete a range of characters [offset, offset + count) */
|
|
12
|
+
export interface DeleteText {
|
|
13
|
+
type: 'delete_text';
|
|
14
|
+
offset: number;
|
|
15
|
+
count: number;
|
|
16
|
+
/** Deleted text stored for undo. Populated by the engine, not the caller. */
|
|
17
|
+
_deleted?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Split a block at a position, creating a new block after it.
|
|
21
|
+
* This is what "pressing Enter" produces.
|
|
22
|
+
*/
|
|
23
|
+
export interface SplitBlock {
|
|
24
|
+
type: 'split_block';
|
|
25
|
+
offset: number;
|
|
26
|
+
/** ID for the newly created block. Generated by engine if omitted. */
|
|
27
|
+
newBlockId?: string;
|
|
28
|
+
/** Original type of the merged block. Populated by merge_block inverse. */
|
|
29
|
+
_mergedBlockType?: string;
|
|
30
|
+
/** Original attrs of the merged block. Populated by merge_block inverse. */
|
|
31
|
+
_mergedBlockAttrs?: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Merge a block with the previous block.
|
|
35
|
+
* This is what "pressing Backspace at the start of a block" produces.
|
|
36
|
+
*/
|
|
37
|
+
export interface MergeBlock {
|
|
38
|
+
type: 'merge_block';
|
|
39
|
+
/** ID of the block being merged into the previous one */
|
|
40
|
+
blockId: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Set/update block-level attributes.
|
|
44
|
+
* Shallow merge with existing attrs. Set a key to `null` to remove it.
|
|
45
|
+
*/
|
|
46
|
+
export interface SetBlockAttrs {
|
|
47
|
+
type: 'set_block_attrs';
|
|
48
|
+
blockId: string;
|
|
49
|
+
attrs: Record<string, unknown>;
|
|
50
|
+
/** Previous attrs stored for undo. Populated by engine. */
|
|
51
|
+
_prevAttrs?: Record<string, unknown>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Change a block's type (e.g., "text" → "heading").
|
|
55
|
+
* The block retains its id, spans, and attrs — only `type` changes.
|
|
56
|
+
*/
|
|
57
|
+
export interface SetBlockType {
|
|
58
|
+
type: 'set_block_type';
|
|
59
|
+
blockId: string;
|
|
60
|
+
/** The new block type string */
|
|
61
|
+
blockType: string;
|
|
62
|
+
/** Previous type stored for undo. Populated by apply(). */
|
|
63
|
+
_prevType?: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Apply/remove marks on a range of text [offset, offset + count).
|
|
67
|
+
* Internally triggers span splitting/merging as needed.
|
|
68
|
+
*/
|
|
69
|
+
export interface SetSpanMarks {
|
|
70
|
+
type: 'set_span_marks';
|
|
71
|
+
offset: number;
|
|
72
|
+
count: number;
|
|
73
|
+
/**
|
|
74
|
+
* Marks to set (shallow merge per span).
|
|
75
|
+
* Set a mark key to `null` to remove it.
|
|
76
|
+
*/
|
|
77
|
+
marks: Record<string, unknown>;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Set document-level metadata.
|
|
81
|
+
* Shallow merge. Set key to `null` to remove.
|
|
82
|
+
*/
|
|
83
|
+
export interface SetMeta {
|
|
84
|
+
type: 'set_meta';
|
|
85
|
+
meta: Record<string, unknown>;
|
|
86
|
+
_prevMeta?: Record<string, unknown>;
|
|
87
|
+
}
|
|
88
|
+
/** Union of all operation types */
|
|
89
|
+
export type Operation = InsertText | DeleteText | SplitBlock | MergeBlock | SetBlockAttrs | SetBlockType | SetSpanMarks | SetMeta;
|
|
90
|
+
//# sourceMappingURL=operations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../src/operations.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,sCAAsC;AACtC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,4DAA4D;AAC5D,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4EAA4E;IAC5E,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7C;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,aAAa,CAAC;IACpB,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,iBAAiB,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,gBAAgB,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED,mCAAmC;AACnC,MAAM,MAAM,SAAS,GACjB,UAAU,GACV,UAAU,GACV,UAAU,GACV,UAAU,GACV,aAAa,GACb,YAAY,GACZ,YAAY,GACZ,OAAO,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.js","sourceRoot":"","sources":["../src/operations.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document query utilities — read-only queries against document state.
|
|
3
|
+
* Used by engine and platform implementations for toolbar state, accessibility, etc.
|
|
4
|
+
*/
|
|
5
|
+
import type { Block, Document } from './model.js';
|
|
6
|
+
/** Result of querying marks across a text range */
|
|
7
|
+
export interface RangeMarksResult {
|
|
8
|
+
/** Marks active across the entire range with identical values */
|
|
9
|
+
readonly common: Record<string, unknown>;
|
|
10
|
+
/** Mark keys present in some spans but not all, or with differing values */
|
|
11
|
+
readonly mixed: readonly string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Get the formatting marks at a specific document offset.
|
|
15
|
+
*
|
|
16
|
+
* At span boundaries, returns the marks of the preceding span
|
|
17
|
+
* (consistent with `insert_text` mark inheritance behavior).
|
|
18
|
+
*
|
|
19
|
+
* @param doc - The document to query
|
|
20
|
+
* @param offset - Absolute character offset
|
|
21
|
+
* @returns A copy of the marks at that position (empty object if no marks)
|
|
22
|
+
* @throws {RtifError} OFFSET_OUT_OF_RANGE if offset is invalid
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* // Given: [{ text: 'bold', marks: { bold: true } }, { text: 'plain' }]
|
|
27
|
+
* getMarksAtOffset(doc, 2); // => { bold: true }
|
|
28
|
+
* getMarksAtOffset(doc, 5); // => {}
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function getMarksAtOffset(doc: Document, offset: number): Record<string, unknown>;
|
|
32
|
+
/**
|
|
33
|
+
* Get the marks across a text range, identifying which are common and which are mixed.
|
|
34
|
+
*
|
|
35
|
+
* Supports cross-block ranges. Virtual `\n` separators between blocks
|
|
36
|
+
* contribute no marks and are skipped.
|
|
37
|
+
*
|
|
38
|
+
* @param doc - The document to query
|
|
39
|
+
* @param offset - Start of the range (absolute offset)
|
|
40
|
+
* @param count - Number of characters in the range
|
|
41
|
+
* @returns Common marks (same across all spans) and mixed mark keys
|
|
42
|
+
* @throws {RtifError} OFFSET_OUT_OF_RANGE if offset is invalid
|
|
43
|
+
* @throws {RtifError} INVALID_COUNT if count is negative or range exceeds document
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* // Given: [{ text: 'bo', marks: { bold: true } }, { text: 'th', marks: { bold: true, italic: true } }]
|
|
48
|
+
* getMarksInRange(doc, 0, 4);
|
|
49
|
+
* // => { common: { bold: true }, mixed: ['italic'] }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function getMarksInRange(doc: Document, offset: number, count: number): RangeMarksResult;
|
|
53
|
+
/**
|
|
54
|
+
* Get the block containing a specific document offset.
|
|
55
|
+
*
|
|
56
|
+
* @param doc - The document to query
|
|
57
|
+
* @param offset - Absolute character offset
|
|
58
|
+
* @returns The block at that offset
|
|
59
|
+
* @throws {RtifError} OFFSET_OUT_OF_RANGE if offset is invalid
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* const block = getBlockAtOffset(doc, 0);
|
|
64
|
+
* // => { id: 'b1', type: 'text', spans: [...] }
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function getBlockAtOffset(doc: Document, offset: number): Block;
|
|
68
|
+
//# sourceMappingURL=queries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAIlD,mDAAmD;AACnD,MAAM,WAAW,gBAAgB;IAC/B,iEAAiE;IACjE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,MAAM,GACb,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAwBzB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,gBAAgB,CA0FlB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CAGrE"}
|
package/dist/queries.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document query utilities — read-only queries against document state.
|
|
3
|
+
* Used by engine and platform implementations for toolbar state, accessibility, etc.
|
|
4
|
+
*/
|
|
5
|
+
import { RtifError } from './error.js';
|
|
6
|
+
import { resolve, blockTextLength, docLength } from './resolve.js';
|
|
7
|
+
/**
|
|
8
|
+
* Get the formatting marks at a specific document offset.
|
|
9
|
+
*
|
|
10
|
+
* At span boundaries, returns the marks of the preceding span
|
|
11
|
+
* (consistent with `insert_text` mark inheritance behavior).
|
|
12
|
+
*
|
|
13
|
+
* @param doc - The document to query
|
|
14
|
+
* @param offset - Absolute character offset
|
|
15
|
+
* @returns A copy of the marks at that position (empty object if no marks)
|
|
16
|
+
* @throws {RtifError} OFFSET_OUT_OF_RANGE if offset is invalid
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* // Given: [{ text: 'bold', marks: { bold: true } }, { text: 'plain' }]
|
|
21
|
+
* getMarksAtOffset(doc, 2); // => { bold: true }
|
|
22
|
+
* getMarksAtOffset(doc, 5); // => {}
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export function getMarksAtOffset(doc, offset) {
|
|
26
|
+
const { blockIndex, localOffset } = resolve(doc, offset);
|
|
27
|
+
const block = doc.blocks[blockIndex];
|
|
28
|
+
// Empty block — single span with no text
|
|
29
|
+
if (block.spans.length === 1 && block.spans[0].text === '') {
|
|
30
|
+
return { ...(block.spans[0].marks ?? {}) };
|
|
31
|
+
}
|
|
32
|
+
let pos = 0;
|
|
33
|
+
for (const span of block.spans) {
|
|
34
|
+
const spanEnd = pos + span.text.length;
|
|
35
|
+
// Inside this span, or at its trailing boundary
|
|
36
|
+
if (localOffset <= spanEnd) {
|
|
37
|
+
return { ...(span.marks ?? {}) };
|
|
38
|
+
}
|
|
39
|
+
pos = spanEnd;
|
|
40
|
+
}
|
|
41
|
+
// Fallback: end of block (shouldn't reach here if resolve is correct)
|
|
42
|
+
const lastSpan = block.spans[block.spans.length - 1];
|
|
43
|
+
return { ...(lastSpan.marks ?? {}) };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get the marks across a text range, identifying which are common and which are mixed.
|
|
47
|
+
*
|
|
48
|
+
* Supports cross-block ranges. Virtual `\n` separators between blocks
|
|
49
|
+
* contribute no marks and are skipped.
|
|
50
|
+
*
|
|
51
|
+
* @param doc - The document to query
|
|
52
|
+
* @param offset - Start of the range (absolute offset)
|
|
53
|
+
* @param count - Number of characters in the range
|
|
54
|
+
* @returns Common marks (same across all spans) and mixed mark keys
|
|
55
|
+
* @throws {RtifError} OFFSET_OUT_OF_RANGE if offset is invalid
|
|
56
|
+
* @throws {RtifError} INVALID_COUNT if count is negative or range exceeds document
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* // Given: [{ text: 'bo', marks: { bold: true } }, { text: 'th', marks: { bold: true, italic: true } }]
|
|
61
|
+
* getMarksInRange(doc, 0, 4);
|
|
62
|
+
* // => { common: { bold: true }, mixed: ['italic'] }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export function getMarksInRange(doc, offset, count) {
|
|
66
|
+
if (count < 0) {
|
|
67
|
+
throw new RtifError('INVALID_COUNT', `Count ${count} is negative`);
|
|
68
|
+
}
|
|
69
|
+
if (count === 0) {
|
|
70
|
+
const marks = getMarksAtOffset(doc, offset);
|
|
71
|
+
return { common: marks, mixed: [] };
|
|
72
|
+
}
|
|
73
|
+
const totalLen = docLength(doc);
|
|
74
|
+
if (offset < 0 || offset > totalLen) {
|
|
75
|
+
throw new RtifError('OFFSET_OUT_OF_RANGE', `Offset ${offset} out of range`);
|
|
76
|
+
}
|
|
77
|
+
if (offset + count > totalLen) {
|
|
78
|
+
throw new RtifError('INVALID_COUNT', `Range [${offset}, ${offset + count}) exceeds document length ${totalLen}`);
|
|
79
|
+
}
|
|
80
|
+
const start = resolve(doc, offset);
|
|
81
|
+
// Collect mark objects from each span (or span fragment) in the range
|
|
82
|
+
const allMarkSets = [];
|
|
83
|
+
let remaining = count;
|
|
84
|
+
let blockIdx = start.blockIndex;
|
|
85
|
+
let localPos = start.localOffset;
|
|
86
|
+
while (remaining > 0 && blockIdx < doc.blocks.length) {
|
|
87
|
+
const block = doc.blocks[blockIdx];
|
|
88
|
+
const blockLen = blockTextLength(block);
|
|
89
|
+
const available = blockLen - localPos;
|
|
90
|
+
const consume = Math.min(remaining, available);
|
|
91
|
+
if (consume > 0) {
|
|
92
|
+
// Walk spans in this block, collecting marks for the overlapping portion
|
|
93
|
+
let spanPos = 0;
|
|
94
|
+
for (const span of block.spans) {
|
|
95
|
+
const spanEnd = spanPos + span.text.length;
|
|
96
|
+
if (spanEnd <= localPos) {
|
|
97
|
+
spanPos = spanEnd;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (spanPos >= localPos + consume)
|
|
101
|
+
break;
|
|
102
|
+
allMarkSets.push(span.marks ?? {});
|
|
103
|
+
spanPos = spanEnd;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
remaining -= consume;
|
|
107
|
+
// Account for virtual \n between blocks
|
|
108
|
+
if (remaining > 0 && blockIdx < doc.blocks.length - 1) {
|
|
109
|
+
remaining -= 1;
|
|
110
|
+
blockIdx++;
|
|
111
|
+
localPos = 0;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (allMarkSets.length === 0) {
|
|
118
|
+
return { common: {}, mixed: [] };
|
|
119
|
+
}
|
|
120
|
+
// Collect all unique mark keys across every span
|
|
121
|
+
const allKeys = new Set();
|
|
122
|
+
for (const marks of allMarkSets) {
|
|
123
|
+
for (const key of Object.keys(marks)) {
|
|
124
|
+
allKeys.add(key);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const common = {};
|
|
128
|
+
const mixed = [];
|
|
129
|
+
for (const key of allKeys) {
|
|
130
|
+
const firstValue = allMarkSets[0][key];
|
|
131
|
+
const isCommon = allMarkSets.every((marks) => key in marks && marks[key] === firstValue);
|
|
132
|
+
if (isCommon) {
|
|
133
|
+
common[key] = firstValue;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
mixed.push(key);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { common, mixed };
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get the block containing a specific document offset.
|
|
143
|
+
*
|
|
144
|
+
* @param doc - The document to query
|
|
145
|
+
* @param offset - Absolute character offset
|
|
146
|
+
* @returns The block at that offset
|
|
147
|
+
* @throws {RtifError} OFFSET_OUT_OF_RANGE if offset is invalid
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* const block = getBlockAtOffset(doc, 0);
|
|
152
|
+
* // => { id: 'b1', type: 'text', spans: [...] }
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export function getBlockAtOffset(doc, offset) {
|
|
156
|
+
const { blockIndex } = resolve(doc, offset);
|
|
157
|
+
return doc.blocks[blockIndex];
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=queries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offset resolution — converts absolute offsets to block-local coordinates.
|
|
3
|
+
* See SPEC.md §2.4 for specification.
|
|
4
|
+
*/
|
|
5
|
+
import type { Block, Document } from './model.js';
|
|
6
|
+
export interface ResolvedPosition {
|
|
7
|
+
/** Index of the block containing this offset */
|
|
8
|
+
readonly blockIndex: number;
|
|
9
|
+
/** Character offset within the block */
|
|
10
|
+
readonly localOffset: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Compute the text length of a block (sum of all span text lengths).
|
|
14
|
+
*
|
|
15
|
+
* @param block - The block to measure
|
|
16
|
+
* @returns Total character count across all spans
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* blockTextLength({ id: 'b1', type: 'text', spans: [{ text: 'hello' }] });
|
|
21
|
+
* // => 5
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function blockTextLength(block: Block): number;
|
|
25
|
+
/**
|
|
26
|
+
* Compute the total document length (all block text + virtual newlines between blocks).
|
|
27
|
+
*
|
|
28
|
+
* @param doc - The document to measure
|
|
29
|
+
* @returns Total character count including virtual newline separators
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* docLength({ version: 1, blocks: [
|
|
34
|
+
* { id: 'b1', type: 'text', spans: [{ text: 'hello' }] },
|
|
35
|
+
* { id: 'b2', type: 'text', spans: [{ text: 'world' }] },
|
|
36
|
+
* ] });
|
|
37
|
+
* // => 11 (5 + 1 + 5)
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function docLength(doc: Document): number;
|
|
41
|
+
/**
|
|
42
|
+
* Resolve an absolute document offset to a block index and local offset.
|
|
43
|
+
*
|
|
44
|
+
* Walks blocks, subtracting lengths (including the +1 separator per block
|
|
45
|
+
* boundary), until the offset lands inside a block.
|
|
46
|
+
*
|
|
47
|
+
* @param doc - The document to resolve against
|
|
48
|
+
* @param offset - Absolute character offset from document start
|
|
49
|
+
* @returns Block index and character offset within that block
|
|
50
|
+
* @throws {RtifError} OFFSET_OUT_OF_RANGE if offset is negative or exceeds document length
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* const { blockIndex, localOffset } = resolve(doc, 5);
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolve(doc: Document, offset: number): ResolvedPosition;
|
|
58
|
+
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGlD,MAAM,WAAW,gBAAgB;IAC/B,gDAAgD;IAChD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,wCAAwC;IACxC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAMpD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,CAO/C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAqCvE"}
|