svedit 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -22
- package/dist/Command.svelte.d.ts +38 -30
- package/dist/Command.svelte.d.ts.map +1 -1
- package/dist/Command.svelte.js +271 -326
- package/dist/CustomProperty.svelte +3 -5
- package/dist/CustomProperty.svelte.d.ts +3 -6
- package/dist/CustomProperty.svelte.d.ts.map +1 -1
- package/dist/KeyMapper.svelte.d.ts +19 -6
- package/dist/KeyMapper.svelte.d.ts.map +1 -1
- package/dist/KeyMapper.svelte.js +95 -112
- package/dist/Node.svelte +16 -13
- package/dist/Node.svelte.d.ts +3 -5
- package/dist/Node.svelte.d.ts.map +1 -1
- package/dist/NodeArrayProperty.svelte +39 -22
- package/dist/NodeArrayProperty.svelte.d.ts +3 -5
- package/dist/NodeArrayProperty.svelte.d.ts.map +1 -1
- package/dist/NodeCaret.svelte +2 -2
- package/dist/NodeCaret.svelte.d.ts +6 -14
- package/dist/NodeCaret.svelte.d.ts.map +1 -1
- package/dist/NodeGap.svelte +12 -6
- package/dist/NodeGap.svelte.d.ts +7 -14
- package/dist/NodeGap.svelte.d.ts.map +1 -1
- package/dist/NodeGapMarkers.svelte +36 -14
- package/dist/NodeGapMarkers.svelte.d.ts +6 -11
- package/dist/NodeGapMarkers.svelte.d.ts.map +1 -1
- package/dist/NodeSelectionMarkers.svelte +21 -7
- package/dist/NodeSelectionMarkers.svelte.d.ts +2 -5
- package/dist/NodeSelectionMarkers.svelte.d.ts.map +1 -1
- package/dist/Session.svelte.d.ts +81 -129
- package/dist/Session.svelte.d.ts.map +1 -1
- package/dist/Session.svelte.js +542 -648
- package/dist/Svedit.svelte +146 -140
- package/dist/Svedit.svelte.d.ts +30 -10
- package/dist/Svedit.svelte.d.ts.map +1 -1
- package/dist/TextProperty.svelte +37 -23
- package/dist/TextProperty.svelte.d.ts +3 -5
- package/dist/TextProperty.svelte.d.ts.map +1 -1
- package/dist/Transaction.svelte.d.ts +64 -127
- package/dist/Transaction.svelte.d.ts.map +1 -1
- package/dist/Transaction.svelte.js +839 -1001
- package/dist/UnknownNode.svelte +4 -3
- package/dist/UnknownNode.svelte.d.ts +5 -9
- package/dist/UnknownNode.svelte.d.ts.map +1 -1
- package/dist/doc_utils.d.ts +67 -155
- package/dist/doc_utils.d.ts.map +1 -1
- package/dist/doc_utils.js +619 -863
- package/dist/index.d.ts +20 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -17
- package/dist/node_visibility.svelte.d.ts +110 -9
- package/dist/node_visibility.svelte.d.ts.map +1 -1
- package/dist/node_visibility.svelte.js +473 -544
- package/dist/paste_utils.d.ts +11 -44
- package/dist/paste_utils.d.ts.map +1 -1
- package/dist/paste_utils.js +111 -152
- package/dist/styles/svedit-colors.css +8 -5
- package/dist/transforms.svelte.d.ts +9 -7
- package/dist/transforms.svelte.d.ts.map +1 -1
- package/dist/transforms.svelte.js +125 -150
- package/dist/types.d.ts +294 -309
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +40 -119
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +346 -473
- package/package.json +70 -70
- package/src/lib/{Command.svelte.js → Command.svelte.ts} +29 -17
- package/src/lib/CustomProperty.svelte +3 -5
- package/src/lib/{KeyMapper.svelte.js → KeyMapper.svelte.ts} +28 -11
- package/src/lib/Node.svelte +16 -13
- package/src/lib/NodeArrayProperty.svelte +39 -22
- package/src/lib/NodeCaret.svelte +2 -2
- package/src/lib/NodeGap.svelte +12 -6
- package/src/lib/NodeGapMarkers.svelte +36 -14
- package/src/lib/NodeSelectionMarkers.svelte +21 -7
- package/src/lib/{Session.svelte.js → Session.svelte.ts} +130 -127
- package/src/lib/Svedit.svelte +146 -140
- package/src/lib/TextProperty.svelte +37 -23
- package/src/lib/{Transaction.svelte.js → Transaction.svelte.ts} +139 -143
- package/src/lib/UnknownNode.svelte +4 -3
- package/src/lib/{doc_utils.js → doc_utils.ts} +247 -206
- package/src/lib/{index.js → index.ts} +48 -0
- package/src/lib/{node_visibility.svelte.js → node_visibility.svelte.ts} +68 -92
- package/src/lib/{paste_utils.js → paste_utils.ts} +47 -58
- package/src/lib/styles/svedit-colors.css +8 -5
- package/src/lib/{transforms.svelte.js → transforms.svelte.ts} +39 -21
- package/src/lib/{types.d.ts → types.ts} +161 -66
- package/src/lib/{utils.js → utils.ts} +110 -129
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* These functions operate on the core document state (schema, doc, selection, config)
|
|
5
5
|
* without any history management or transaction tracking.
|
|
6
|
-
*
|
|
7
|
-
* @import { NodeId, DocumentPath, PrimitiveType, NodeProperty, NodeArrayProperty, NodeSchema, DocumentSchema, Selection, Attachment, Mark, Annotation, Document, TextProperty, AnnotatedText, ValidateDocumentSchema } from './types'
|
|
8
6
|
*/
|
|
9
7
|
|
|
10
8
|
import {
|
|
@@ -15,25 +13,44 @@ import {
|
|
|
15
13
|
serialize_path,
|
|
16
14
|
are_ranges_exclusive
|
|
17
15
|
} from './utils.js';
|
|
16
|
+
import type {
|
|
17
|
+
NodeId,
|
|
18
|
+
DocumentPath,
|
|
19
|
+
PrimitiveType,
|
|
20
|
+
PropertyDefinition,
|
|
21
|
+
NodeProperty,
|
|
22
|
+
NodeArrayProperty,
|
|
23
|
+
NodeSchema,
|
|
24
|
+
NodeKind,
|
|
25
|
+
DocumentSchema,
|
|
26
|
+
Selection,
|
|
27
|
+
Attachment,
|
|
28
|
+
Mark,
|
|
29
|
+
Annotation,
|
|
30
|
+
Document,
|
|
31
|
+
DocumentNode,
|
|
32
|
+
TextProperty,
|
|
33
|
+
Text,
|
|
34
|
+
ValidateDocumentSchema,
|
|
35
|
+
Inspection,
|
|
36
|
+
DocumentOperation,
|
|
37
|
+
SessionConfig
|
|
38
|
+
} from './types.js';
|
|
18
39
|
|
|
19
40
|
/**
|
|
20
41
|
* Identity function — keeps schema at runtime & makes IDE infer types.
|
|
21
42
|
* Similar to your define_schema pattern but for document schemas.
|
|
22
|
-
*
|
|
23
|
-
* @template {Record<string, NodeSchema>} S
|
|
24
|
-
* @param {S & ValidateDocumentSchema<S>} schema - The document schema to validate
|
|
25
|
-
* @returns {S} The same schema, but with type information preserved
|
|
26
43
|
*/
|
|
27
|
-
export function define_document_schema(
|
|
44
|
+
export function define_document_schema<S extends Record<string, NodeSchema>>(
|
|
45
|
+
schema: S & ValidateDocumentSchema<S>
|
|
46
|
+
): S {
|
|
28
47
|
return schema;
|
|
29
48
|
}
|
|
30
49
|
|
|
31
50
|
/**
|
|
32
51
|
* Check if a string represents a valid primitive type.
|
|
33
|
-
* @param {string} type - The type string to check
|
|
34
|
-
* @returns {type is PrimitiveType} True if it's a valid primitive type
|
|
35
52
|
*/
|
|
36
|
-
export function is_primitive_type(type) {
|
|
53
|
+
export function is_primitive_type(type: string): type is PrimitiveType {
|
|
37
54
|
return [
|
|
38
55
|
'string',
|
|
39
56
|
'number',
|
|
@@ -50,10 +67,11 @@ export function is_primitive_type(type) {
|
|
|
50
67
|
|
|
51
68
|
/**
|
|
52
69
|
* Get the default node type for a property that references nodes.
|
|
53
|
-
*
|
|
54
|
-
* @returns {string | null} The default node type, or null if none specified
|
|
70
|
+
* Returns null if none specified.
|
|
55
71
|
*/
|
|
56
|
-
export function get_default_node_type(
|
|
72
|
+
export function get_default_node_type(
|
|
73
|
+
property_definition: NodeProperty | NodeArrayProperty
|
|
74
|
+
): string | null {
|
|
57
75
|
if (!property_definition || !property_definition.node_types) {
|
|
58
76
|
return null;
|
|
59
77
|
}
|
|
@@ -64,11 +82,8 @@ export function get_default_node_type(property_definition) {
|
|
|
64
82
|
);
|
|
65
83
|
}
|
|
66
84
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
* @returns {any}
|
|
70
|
-
*/
|
|
71
|
-
export function get_property_default(property_definition) {
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
86
|
+
export function get_property_default(property_definition: PropertyDefinition): any {
|
|
72
87
|
if ('default' in property_definition) return structuredClone(property_definition.default);
|
|
73
88
|
|
|
74
89
|
if (property_definition.type === 'string') return '';
|
|
@@ -97,11 +112,9 @@ export function get_property_default(property_definition) {
|
|
|
97
112
|
* schema changes cannot be represented by defaults, such as property renames or
|
|
98
113
|
* data transformations.
|
|
99
114
|
*
|
|
100
|
-
* @
|
|
101
|
-
* @param {DocumentSchema} schema - The document schema
|
|
102
|
-
* @returns {any} A shallow copy of the node with cloned default values filled in
|
|
115
|
+
* @returns A shallow copy of the node with cloned default values filled in
|
|
103
116
|
*/
|
|
104
|
-
export function fill_node_defaults(node, schema) {
|
|
117
|
+
export function fill_node_defaults(node: DocumentNode, schema: DocumentSchema): DocumentNode {
|
|
105
118
|
const node_schema = schema[node.type];
|
|
106
119
|
if (!node_schema) return { ...node };
|
|
107
120
|
|
|
@@ -124,13 +137,10 @@ export function fill_node_defaults(node, schema) {
|
|
|
124
137
|
* by itself. Call this as one step in an explicit document migration when it is
|
|
125
138
|
* appropriate.
|
|
126
139
|
*
|
|
127
|
-
* @
|
|
128
|
-
* @param {DocumentSchema} schema - The document schema
|
|
129
|
-
* @returns {Document} A document copy with cloned default values filled in
|
|
140
|
+
* @returns A document copy with cloned default values filled in
|
|
130
141
|
*/
|
|
131
|
-
export function fill_document_defaults(doc, schema) {
|
|
132
|
-
|
|
133
|
-
const nodes = {};
|
|
142
|
+
export function fill_document_defaults(doc: Document, schema: DocumentSchema): Document {
|
|
143
|
+
const nodes: Record<string, DocumentNode> = {};
|
|
134
144
|
|
|
135
145
|
for (const [node_id, node] of Object.entries(doc.nodes)) {
|
|
136
146
|
nodes[node_id] = fill_node_defaults(node, schema);
|
|
@@ -144,14 +154,73 @@ export function fill_document_defaults(doc, schema) {
|
|
|
144
154
|
|
|
145
155
|
/**
|
|
146
156
|
* Validate a document schema to ensure all referenced node types exist.
|
|
147
|
-
* @param {DocumentSchema} document_schema - The document schema to validate
|
|
148
157
|
* @throws {Error} Throws if the document schema is invalid
|
|
149
158
|
*/
|
|
150
|
-
export function validate_document_schema(document_schema) {
|
|
159
|
+
export function validate_document_schema(document_schema: DocumentSchema): void {
|
|
151
160
|
// Check that all referenced node types exist
|
|
152
161
|
for (const [node_type, node_schema] of Object.entries(document_schema)) {
|
|
153
162
|
for (const [prop_name, prop_def] of Object.entries(node_schema.properties)) {
|
|
154
163
|
assert_path_string_segment(prop_name, `Property name "${prop_name}"`);
|
|
164
|
+
if (prop_def.type === 'string' && prop_def.values !== undefined) {
|
|
165
|
+
if (
|
|
166
|
+
!Array.isArray(prop_def.values) ||
|
|
167
|
+
prop_def.values.some((value) => typeof value !== 'string')
|
|
168
|
+
) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Node type "${node_type}" property "${prop_name}" values must be an array of strings.`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
if (prop_def.values.length === 0) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`Node type "${node_type}" property "${prop_name}" values must not be empty.`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
if (new Set(prop_def.values).size !== prop_def.values.length) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`Node type "${node_type}" property "${prop_name}" values must be unique.`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
if (prop_def.default !== undefined && !prop_def.values.includes(prop_def.default)) {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`Node type "${node_type}" property "${prop_name}" default must be one of its allowed values.`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (prop_def.type === 'integer') {
|
|
190
|
+
if (prop_def.default !== undefined && !Number.isInteger(prop_def.default)) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`Node type "${node_type}" property "${prop_name}" default must be an integer.`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
if (prop_def.min !== undefined && !Number.isInteger(prop_def.min)) {
|
|
196
|
+
throw new Error(
|
|
197
|
+
`Node type "${node_type}" property "${prop_name}" min must be an integer.`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
if (prop_def.max !== undefined && !Number.isInteger(prop_def.max)) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`Node type "${node_type}" property "${prop_name}" max must be an integer.`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
if (
|
|
206
|
+
prop_def.min !== undefined &&
|
|
207
|
+
prop_def.max !== undefined &&
|
|
208
|
+
prop_def.min > prop_def.max
|
|
209
|
+
) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`Node type "${node_type}" property "${prop_name}" min must not be greater than max.`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
if (
|
|
215
|
+
prop_def.default !== undefined &&
|
|
216
|
+
((prop_def.min !== undefined && prop_def.default < prop_def.min) ||
|
|
217
|
+
(prop_def.max !== undefined && prop_def.default > prop_def.max))
|
|
218
|
+
) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`Node type "${node_type}" property "${prop_name}" default must be within its min/max range.`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
155
224
|
if (prop_def.type === 'node' || prop_def.type === 'node_array') {
|
|
156
225
|
const missing_types = prop_def.node_types.filter(
|
|
157
226
|
(ref_type) => !(ref_type in document_schema)
|
|
@@ -167,7 +236,7 @@ export function validate_document_schema(document_schema) {
|
|
|
167
236
|
for (const [key, expected_kind] of [
|
|
168
237
|
['mark_types', 'mark'],
|
|
169
238
|
['annotation_types', 'annotation']
|
|
170
|
-
]) {
|
|
239
|
+
] as const) {
|
|
171
240
|
const invalid_types = (prop_def[key] ?? []).filter(
|
|
172
241
|
(ref_type) => document_schema[ref_type]?.kind !== expected_kind
|
|
173
242
|
);
|
|
@@ -188,11 +257,9 @@ export function validate_document_schema(document_schema) {
|
|
|
188
257
|
* Marks render in-place via components; annotations are data-only overlay
|
|
189
258
|
* ranges that must be interpreted by the app (via CSS classes or props).
|
|
190
259
|
*
|
|
191
|
-
* @param {DocumentSchema} schema - The document schema
|
|
192
|
-
* @param {any} config - The Svedit config
|
|
193
260
|
* @throws {Error} Throws if a kind 'annotation' type has a registered component
|
|
194
261
|
*/
|
|
195
|
-
export function validate_config_components(schema, config) {
|
|
262
|
+
export function validate_config_components(schema: DocumentSchema, config: SessionConfig): void {
|
|
196
263
|
for (const [node_type, node_schema] of Object.entries(schema)) {
|
|
197
264
|
if (node_schema.kind === 'annotation' && config?.node_components?.[node_type]) {
|
|
198
265
|
throw new Error(
|
|
@@ -204,11 +271,9 @@ export function validate_config_components(schema, config) {
|
|
|
204
271
|
|
|
205
272
|
/**
|
|
206
273
|
* Validate a primitive value against its schema type.
|
|
207
|
-
* @param {PrimitiveType} type - The expected type
|
|
208
|
-
* @param {any} value - The value to validate
|
|
209
|
-
* @returns {boolean} True if the value matches the type
|
|
210
274
|
*/
|
|
211
|
-
|
|
275
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
276
|
+
function validate_primitive_value(type: PrimitiveType, value: any): boolean {
|
|
212
277
|
switch (type) {
|
|
213
278
|
case 'string':
|
|
214
279
|
return typeof value === 'string';
|
|
@@ -244,26 +309,18 @@ function validate_primitive_value(type, value) {
|
|
|
244
309
|
/**
|
|
245
310
|
* Validate ranges (marks or annotations) for bounds, references, and allowed types.
|
|
246
311
|
*
|
|
247
|
-
* @param {string} node_id - Owner node id, used for error messages
|
|
248
|
-
* @param {string} prop_name - Owner property name, used for error messages
|
|
249
|
-
* @param {'mark' | 'annotation'} label - Range label, used for error messages
|
|
250
|
-
* @param {Array<Attachment>} ranges - Ranges to validate
|
|
251
|
-
* @param {number} container_length - Length of the annotated text or node array
|
|
252
|
-
* @param {Array<string> | null | undefined} allowed_types - Allowed node types
|
|
253
|
-
* @param {Record<string, any>} all_nodes - All document nodes
|
|
254
|
-
* @param {boolean} require_references - Whether referenced nodes must already exist
|
|
255
312
|
* @throws {Error} Throws if ranges are invalid
|
|
256
313
|
*/
|
|
257
314
|
function validate_range_array(
|
|
258
|
-
node_id,
|
|
259
|
-
prop_name,
|
|
260
|
-
label,
|
|
261
|
-
ranges
|
|
262
|
-
container_length,
|
|
263
|
-
allowed_types,
|
|
264
|
-
all_nodes,
|
|
265
|
-
require_references
|
|
266
|
-
) {
|
|
315
|
+
node_id: string,
|
|
316
|
+
prop_name: string,
|
|
317
|
+
label: 'mark' | 'annotation',
|
|
318
|
+
ranges: Array<Attachment>,
|
|
319
|
+
container_length: number,
|
|
320
|
+
allowed_types: Array<string> | null | undefined,
|
|
321
|
+
all_nodes: Record<string, DocumentNode>,
|
|
322
|
+
require_references: boolean
|
|
323
|
+
): void {
|
|
267
324
|
for (const [index, range] of ranges.entries()) {
|
|
268
325
|
if (
|
|
269
326
|
typeof range !== 'object' ||
|
|
@@ -312,24 +369,17 @@ function validate_range_array(
|
|
|
312
369
|
*
|
|
313
370
|
* Marks must be mutually exclusive; annotations may overlap.
|
|
314
371
|
*
|
|
315
|
-
* @param {string} node_id - Owner node id, used for error messages
|
|
316
|
-
* @param {string} prop_name - Owner property name, used for error messages
|
|
317
|
-
* @param {{ marks: Array<Mark>, annotations: Array<Annotation> }} value - Annotated value to validate
|
|
318
|
-
* @param {{ mark_types?: string[], annotation_types?: string[] }} prop_def - Property definition
|
|
319
|
-
* @param {number} container_length - Length of the annotated text or node array
|
|
320
|
-
* @param {Record<string, any>} all_nodes - All document nodes
|
|
321
|
-
* @param {boolean} require_references - Whether referenced nodes must already exist
|
|
322
372
|
* @throws {Error} Throws if marks or annotations are invalid
|
|
323
373
|
*/
|
|
324
374
|
function validate_marks_and_annotations(
|
|
325
|
-
node_id,
|
|
326
|
-
prop_name,
|
|
327
|
-
value,
|
|
328
|
-
prop_def,
|
|
329
|
-
container_length,
|
|
330
|
-
all_nodes,
|
|
331
|
-
require_references
|
|
332
|
-
) {
|
|
375
|
+
node_id: string,
|
|
376
|
+
prop_name: string,
|
|
377
|
+
value: { marks: Array<Mark>; annotations: Array<Annotation> },
|
|
378
|
+
prop_def: { mark_types?: string[]; annotation_types?: string[] },
|
|
379
|
+
container_length: number,
|
|
380
|
+
all_nodes: Record<string, DocumentNode>,
|
|
381
|
+
require_references: boolean
|
|
382
|
+
): void {
|
|
333
383
|
validate_range_array(
|
|
334
384
|
node_id,
|
|
335
385
|
prop_name,
|
|
@@ -363,22 +413,16 @@ function validate_marks_and_annotations(
|
|
|
363
413
|
* Validate text property marks and annotations for bounds, references, allowed
|
|
364
414
|
* types, and mark exclusivity.
|
|
365
415
|
*
|
|
366
|
-
* @param {string} node_id - Owner node id, used for error messages
|
|
367
|
-
* @param {string} prop_name - Owner property name, used for error messages
|
|
368
|
-
* @param {AnnotatedText} value - Annotated text value to validate
|
|
369
|
-
* @param {TextProperty} prop_def - Text property definition
|
|
370
|
-
* @param {Record<string, any>} all_nodes - All document nodes
|
|
371
|
-
* @param {boolean} require_references - Whether referenced nodes must already exist
|
|
372
416
|
* @throws {Error} Throws if marks or annotations are invalid
|
|
373
417
|
*/
|
|
374
418
|
function validate_text_property(
|
|
375
|
-
node_id,
|
|
376
|
-
prop_name,
|
|
377
|
-
value,
|
|
378
|
-
prop_def,
|
|
379
|
-
all_nodes,
|
|
380
|
-
require_references
|
|
381
|
-
) {
|
|
419
|
+
node_id: string,
|
|
420
|
+
prop_name: string,
|
|
421
|
+
value: Text,
|
|
422
|
+
prop_def: TextProperty,
|
|
423
|
+
all_nodes: Record<string, DocumentNode>,
|
|
424
|
+
require_references: boolean
|
|
425
|
+
): void {
|
|
382
426
|
const char_length = get_char_length(value.content);
|
|
383
427
|
validate_marks_and_annotations(
|
|
384
428
|
node_id,
|
|
@@ -391,23 +435,25 @@ function validate_text_property(
|
|
|
391
435
|
);
|
|
392
436
|
}
|
|
393
437
|
|
|
394
|
-
|
|
395
|
-
* @param {string} id
|
|
396
|
-
* @returns {boolean}
|
|
397
|
-
*/
|
|
398
|
-
export function is_id_valid(id) {
|
|
438
|
+
export function is_id_valid(id: string): boolean {
|
|
399
439
|
return typeof id === 'string' && id.length > 0 && is_path_string_segment_valid(id);
|
|
400
440
|
}
|
|
401
441
|
|
|
402
442
|
/**
|
|
403
443
|
* Validate a node against its schema.
|
|
404
|
-
*
|
|
405
|
-
* @param
|
|
406
|
-
* @param
|
|
407
|
-
* @param
|
|
444
|
+
*
|
|
445
|
+
* @param node - The node to validate
|
|
446
|
+
* @param schema - The document schema
|
|
447
|
+
* @param all_nodes - All nodes in the document to check references
|
|
448
|
+
* @param options - Validation options
|
|
408
449
|
* @throws {Error} Throws if the node is invalid
|
|
409
450
|
*/
|
|
410
|
-
export function validate_node(
|
|
451
|
+
export function validate_node(
|
|
452
|
+
node: DocumentNode,
|
|
453
|
+
schema: DocumentSchema,
|
|
454
|
+
all_nodes: Record<string, DocumentNode> = {},
|
|
455
|
+
options: { require_references?: boolean } = {}
|
|
456
|
+
): void {
|
|
411
457
|
const require_references = options.require_references ?? true;
|
|
412
458
|
if (!is_id_valid(node.id)) {
|
|
413
459
|
throw new Error(`Node ${node.id} has an invalid id.`);
|
|
@@ -429,13 +475,27 @@ export function validate_node(node, schema, all_nodes = {}, options = {}) {
|
|
|
429
475
|
`Node ${node.id} has an invalid property: ${prop_name} must be of type ${prop_def.type}.`
|
|
430
476
|
);
|
|
431
477
|
}
|
|
478
|
+
if (prop_def.type === 'string' && prop_def.values && !prop_def.values.includes(value)) {
|
|
479
|
+
throw new Error(
|
|
480
|
+
`Node ${node.id} has an invalid property: ${prop_name} must be one of [${prop_def.values.join(', ')}].`
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
if (
|
|
484
|
+
prop_def.type === 'integer' &&
|
|
485
|
+
((prop_def.min !== undefined && value < prop_def.min) ||
|
|
486
|
+
(prop_def.max !== undefined && value > prop_def.max))
|
|
487
|
+
) {
|
|
488
|
+
throw new Error(
|
|
489
|
+
`Node ${node.id} has an invalid property: ${prop_name} must be between ${prop_def.min ?? '-Infinity'} and ${prop_def.max ?? 'Infinity'}.`
|
|
490
|
+
);
|
|
491
|
+
}
|
|
432
492
|
}
|
|
433
493
|
if (prop_def.type === 'text') {
|
|
434
494
|
validate_text_property(
|
|
435
495
|
node.id,
|
|
436
496
|
prop_name,
|
|
437
497
|
value,
|
|
438
|
-
|
|
498
|
+
prop_def as TextProperty,
|
|
439
499
|
all_nodes,
|
|
440
500
|
require_references
|
|
441
501
|
);
|
|
@@ -459,7 +519,7 @@ export function validate_node(node, schema, all_nodes = {}, options = {}) {
|
|
|
459
519
|
}
|
|
460
520
|
if (!prop_def.node_types.includes(referenced_node.type)) {
|
|
461
521
|
throw new Error(
|
|
462
|
-
`Node ${node.id} property ${prop_name} references node ${value} of type ${referenced_node.type}, but only types [${
|
|
522
|
+
`Node ${node.id} property ${prop_name} references node ${value} of type ${referenced_node.type}, but only types [${(prop_def as NodeProperty).node_types.join(', ')}] are allowed.`
|
|
463
523
|
);
|
|
464
524
|
}
|
|
465
525
|
}
|
|
@@ -479,7 +539,7 @@ export function validate_node(node, schema, all_nodes = {}, options = {}) {
|
|
|
479
539
|
|
|
480
540
|
const node_array_nodes = value.nodes;
|
|
481
541
|
|
|
482
|
-
if (!node_array_nodes.every((id) => typeof id === 'string' && is_id_valid(id))) {
|
|
542
|
+
if (!node_array_nodes.every((id: unknown) => typeof id === 'string' && is_id_valid(id))) {
|
|
483
543
|
throw new Error(
|
|
484
544
|
`Node ${node.id} has an invalid property: ${prop_name} must contain valid node ids.`
|
|
485
545
|
);
|
|
@@ -518,11 +578,9 @@ export function validate_node(node, schema, all_nodes = {}, options = {}) {
|
|
|
518
578
|
/**
|
|
519
579
|
* Validate a document against its schema.
|
|
520
580
|
*
|
|
521
|
-
* @param {Document} doc - The document to validate
|
|
522
|
-
* @param {DocumentSchema} schema - The document schema
|
|
523
581
|
* @throws {Error} Throws if the document is invalid
|
|
524
582
|
*/
|
|
525
|
-
export function validate_document(doc, schema) {
|
|
583
|
+
export function validate_document(doc: Document, schema: DocumentSchema): void {
|
|
526
584
|
if (!is_id_valid(doc.document_id)) {
|
|
527
585
|
throw new Error(`Document ${doc.document_id} has an invalid id.`);
|
|
528
586
|
}
|
|
@@ -540,12 +598,13 @@ export function validate_document(doc, schema) {
|
|
|
540
598
|
/**
|
|
541
599
|
* Gets a value from the document at the specified path.
|
|
542
600
|
*
|
|
543
|
-
* @param
|
|
544
|
-
* @param
|
|
545
|
-
* @param
|
|
546
|
-
* @returns
|
|
601
|
+
* @param schema - The document schema
|
|
602
|
+
* @param doc - The document containing nodes
|
|
603
|
+
* @param path - Array path to the value, or a string node ID
|
|
604
|
+
* @returns The value at the specified path
|
|
547
605
|
*/
|
|
548
|
-
|
|
606
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
607
|
+
export function get(schema: DocumentSchema, doc: Document, path: DocumentPath | string): any {
|
|
549
608
|
if (typeof path === 'string') {
|
|
550
609
|
path = [path];
|
|
551
610
|
}
|
|
@@ -553,7 +612,8 @@ export function get(schema, doc, path) {
|
|
|
553
612
|
throw new Error(`Invalid path provided ${JSON.stringify(path)}`);
|
|
554
613
|
}
|
|
555
614
|
|
|
556
|
-
|
|
615
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
616
|
+
let val: any = doc.nodes[path[0]];
|
|
557
617
|
let val_type = 'node';
|
|
558
618
|
|
|
559
619
|
for (let i = 1; i < path.length; i++) {
|
|
@@ -644,13 +704,8 @@ export function get(schema, doc, path) {
|
|
|
644
704
|
|
|
645
705
|
/**
|
|
646
706
|
* Gets the type of a property from the schema.
|
|
647
|
-
*
|
|
648
|
-
* @param {object} schema - The document schema
|
|
649
|
-
* @param {string} type - The node type
|
|
650
|
-
* @param {string} property - The property name
|
|
651
|
-
* @returns {string} The property type
|
|
652
707
|
*/
|
|
653
|
-
export function property_type(schema, type, property) {
|
|
708
|
+
export function property_type(schema: DocumentSchema, type: string, property: string): string {
|
|
654
709
|
if (typeof type !== 'string') throw new Error(`Invalid type ${type} provided`);
|
|
655
710
|
if (typeof property !== 'string') throw new Error(`Invalid property ${property} provided`);
|
|
656
711
|
|
|
@@ -665,28 +720,19 @@ export function property_type(schema, type, property) {
|
|
|
665
720
|
}
|
|
666
721
|
|
|
667
722
|
/**
|
|
668
|
-
* Determines the kind of a node ('block', 'text', 'mark', or 'annotation').
|
|
669
|
-
*
|
|
670
|
-
* @param {object} schema - The document schema
|
|
671
|
-
* @param {any} node - The node to check
|
|
672
|
-
* @returns {'block'|'text'|'mark'|'annotation'} The node kind
|
|
723
|
+
* Determines the kind of a node ('document', 'block', 'text', 'mark', or 'annotation').
|
|
673
724
|
*/
|
|
674
|
-
export function kind(schema, node) {
|
|
725
|
+
export function kind(schema: DocumentSchema, node: DocumentNode): NodeKind {
|
|
675
726
|
return schema[node.type].kind;
|
|
676
727
|
}
|
|
677
728
|
|
|
678
729
|
/**
|
|
679
730
|
* Inspects a path to get metadata about the value at that location.
|
|
680
|
-
*
|
|
681
|
-
* @param {DocumentSchema} schema - The document schema
|
|
682
|
-
* @param {Document} doc - The document containing nodes
|
|
683
|
-
* @param {DocumentPath} path - The path to inspect
|
|
684
|
-
* @returns {{kind: 'property'|'node', [key: string]: any}} Metadata about the path
|
|
685
731
|
*/
|
|
686
|
-
export function inspect(schema, doc, path) {
|
|
732
|
+
export function inspect(schema: DocumentSchema, doc: Document, path: DocumentPath): Inspection {
|
|
687
733
|
const parent = path.length > 1 ? get(schema, doc, path.slice(0, -1)) : undefined;
|
|
688
734
|
if (parent?.type) {
|
|
689
|
-
const property_name = path.at(-1);
|
|
735
|
+
const property_name = path.at(-1) as string;
|
|
690
736
|
return {
|
|
691
737
|
kind: 'property',
|
|
692
738
|
name: property_name,
|
|
@@ -712,10 +758,9 @@ export function inspect(schema, doc, path) {
|
|
|
712
758
|
* (fine-grained rendering relies on this) and the original document is
|
|
713
759
|
* never touched.
|
|
714
760
|
*
|
|
715
|
-
* @
|
|
716
|
-
* @returns {Document} A draft document safe to mutate via apply_op_to_draft
|
|
761
|
+
* @returns A draft document safe to mutate via apply_op_to_draft
|
|
717
762
|
*/
|
|
718
|
-
export function create_document_draft(doc) {
|
|
763
|
+
export function create_document_draft(doc: Document): Document {
|
|
719
764
|
return {
|
|
720
765
|
...doc,
|
|
721
766
|
nodes: { ...doc.nodes }
|
|
@@ -730,11 +775,11 @@ export function create_document_draft(doc) {
|
|
|
730
775
|
* previous one-copy-per-op behavior, which made transactions with many ops
|
|
731
776
|
* (paste, cascade delete) O(N·ops).
|
|
732
777
|
*
|
|
733
|
-
* @param
|
|
734
|
-
* @param
|
|
735
|
-
* @returns
|
|
778
|
+
* @param draft - A draft created via create_document_draft
|
|
779
|
+
* @param op - The operation to apply [type, ...args]
|
|
780
|
+
* @returns The same draft, for convenience
|
|
736
781
|
*/
|
|
737
|
-
export function apply_op_to_draft(draft, op) {
|
|
782
|
+
export function apply_op_to_draft(draft: Document, op: DocumentOperation): Document {
|
|
738
783
|
const [type, ...args] = op;
|
|
739
784
|
if (type === 'set') {
|
|
740
785
|
const [node_id, property] = args[0];
|
|
@@ -754,23 +799,18 @@ export function apply_op_to_draft(draft, op) {
|
|
|
754
799
|
* Applies an operation to a document and returns the new document.
|
|
755
800
|
* Uses copy-on-write semantics.
|
|
756
801
|
*
|
|
757
|
-
* @param
|
|
758
|
-
* @param
|
|
759
|
-
* @returns
|
|
802
|
+
* @param doc - The document to apply the operation to
|
|
803
|
+
* @param op - The operation to apply [type, ...args]
|
|
804
|
+
* @returns The new document with the operation applied
|
|
760
805
|
*/
|
|
761
|
-
export function apply_op(doc, op) {
|
|
806
|
+
export function apply_op(doc: Document, op: DocumentOperation): Document {
|
|
762
807
|
return apply_op_to_draft(create_document_draft(doc), op);
|
|
763
808
|
}
|
|
764
809
|
|
|
765
810
|
/**
|
|
766
811
|
* Counts how many times a node is referenced in the document.
|
|
767
|
-
*
|
|
768
|
-
* @param {DocumentSchema} schema - The document schema
|
|
769
|
-
* @param {Document} doc - The document containing nodes
|
|
770
|
-
* @param {NodeId} node_id - The node ID to count references for
|
|
771
|
-
* @returns {number} The number of references
|
|
772
812
|
*/
|
|
773
|
-
export function count_references(schema, doc, node_id) {
|
|
813
|
+
export function count_references(schema: DocumentSchema, doc: Document, node_id: NodeId): number {
|
|
774
814
|
let count = 0;
|
|
775
815
|
|
|
776
816
|
for (const node of Object.values(doc.nodes)) {
|
|
@@ -780,7 +820,7 @@ export function count_references(schema, doc, node_id) {
|
|
|
780
820
|
const prop_type = property_type(schema, node.type, property);
|
|
781
821
|
|
|
782
822
|
if (prop_type === 'node_array') {
|
|
783
|
-
count += value.nodes.filter((id) => id === node_id).length;
|
|
823
|
+
count += value.nodes.filter((id: NodeId) => id === node_id).length;
|
|
784
824
|
} else if (prop_type === 'node' && value === node_id) {
|
|
785
825
|
count += 1;
|
|
786
826
|
}
|
|
@@ -797,12 +837,13 @@ export function count_references(schema, doc, node_id) {
|
|
|
797
837
|
}
|
|
798
838
|
|
|
799
839
|
/**
|
|
800
|
-
*
|
|
801
|
-
* @param {string} from_type - Current mark type
|
|
802
|
-
* @param {string} to_type - Target mark type
|
|
803
|
-
* @returns {boolean} Whether a mark can be switched between these types
|
|
840
|
+
* Whether a mark can be switched between these types.
|
|
804
841
|
*/
|
|
805
|
-
export function can_switch_mark_type(
|
|
842
|
+
export function can_switch_mark_type(
|
|
843
|
+
schema: DocumentSchema,
|
|
844
|
+
from_type: string,
|
|
845
|
+
to_type: string
|
|
846
|
+
): boolean {
|
|
806
847
|
const from_schema = schema[from_type];
|
|
807
848
|
const to_schema = schema[to_type];
|
|
808
849
|
return (
|
|
@@ -813,6 +854,12 @@ export function can_switch_mark_type(schema, from_type, to_type) {
|
|
|
813
854
|
);
|
|
814
855
|
}
|
|
815
856
|
|
|
857
|
+
/**
|
|
858
|
+
* An attachment (mark or annotation) enriched with its index in the parent
|
|
859
|
+
* attachment array and the resolved payload node.
|
|
860
|
+
*/
|
|
861
|
+
export type SelectedAttachment = Attachment & { index: number; node: DocumentNode };
|
|
862
|
+
|
|
816
863
|
/**
|
|
817
864
|
* Gets ranges of the given key ('marks' or 'annotations') touched by the
|
|
818
865
|
* current selection.
|
|
@@ -821,14 +868,13 @@ export function can_switch_mark_type(schema, from_type, to_type) {
|
|
|
821
868
|
* adjacent boundaries do not count as touching. Collapsed text carets are only
|
|
822
869
|
* inside a range when they are strictly between start and end; a caret at
|
|
823
870
|
* either boundary is not active.
|
|
824
|
-
*
|
|
825
|
-
* @param {DocumentSchema} schema - The document schema
|
|
826
|
-
* @param {Document} doc - The document containing nodes
|
|
827
|
-
* @param {Selection | null | undefined} selection - The current selection
|
|
828
|
-
* @param {'marks' | 'annotations'} key - Which range array to inspect
|
|
829
|
-
* @returns {(Attachment & { index: number, node: any })[]} Selected attachment records
|
|
830
871
|
*/
|
|
831
|
-
function get_selected_ranges(
|
|
872
|
+
function get_selected_ranges(
|
|
873
|
+
schema: DocumentSchema,
|
|
874
|
+
doc: Document,
|
|
875
|
+
selection: Selection | null | undefined,
|
|
876
|
+
key: 'marks' | 'annotations'
|
|
877
|
+
): SelectedAttachment[] {
|
|
832
878
|
if (selection?.type !== 'text' && selection?.type !== 'node') return [];
|
|
833
879
|
const range = get_selection_range(selection);
|
|
834
880
|
if (!range) return [];
|
|
@@ -838,7 +884,7 @@ function get_selected_ranges(schema, doc, selection, key) {
|
|
|
838
884
|
const is_collapsed = range.start_offset === range.end_offset;
|
|
839
885
|
|
|
840
886
|
return ranges
|
|
841
|
-
.map((
|
|
887
|
+
.map((attachment: Attachment, index: number) => {
|
|
842
888
|
const node = doc.nodes[attachment.node_id];
|
|
843
889
|
return {
|
|
844
890
|
...attachment,
|
|
@@ -846,7 +892,7 @@ function get_selected_ranges(schema, doc, selection, key) {
|
|
|
846
892
|
node
|
|
847
893
|
};
|
|
848
894
|
})
|
|
849
|
-
.filter(({ start_offset, end_offset }) => {
|
|
895
|
+
.filter(({ start_offset, end_offset }: SelectedAttachment) => {
|
|
850
896
|
if (is_collapsed) {
|
|
851
897
|
return start_offset < range.start_offset && end_offset > range.start_offset;
|
|
852
898
|
}
|
|
@@ -856,46 +902,44 @@ function get_selected_ranges(schema, doc, selection, key) {
|
|
|
856
902
|
|
|
857
903
|
/**
|
|
858
904
|
* Gets marks touched by the current selection.
|
|
859
|
-
*
|
|
860
|
-
* @param {DocumentSchema} schema - The document schema
|
|
861
|
-
* @param {Document} doc - The document containing nodes
|
|
862
|
-
* @param {Selection} [selection] - The current selection
|
|
863
|
-
* @returns {(Mark & { index: number, node: any })[]} Selected mark records
|
|
864
905
|
*/
|
|
865
|
-
export function get_selected_marks(
|
|
906
|
+
export function get_selected_marks(
|
|
907
|
+
schema: DocumentSchema,
|
|
908
|
+
doc: Document,
|
|
909
|
+
selection?: Selection | null
|
|
910
|
+
): SelectedAttachment[] {
|
|
866
911
|
return get_selected_ranges(schema, doc, selection, 'marks');
|
|
867
912
|
}
|
|
868
913
|
|
|
869
914
|
/**
|
|
870
915
|
* Gets annotations touched by the current selection.
|
|
871
|
-
*
|
|
872
|
-
* @param {DocumentSchema} schema - The document schema
|
|
873
|
-
* @param {Document} doc - The document containing nodes
|
|
874
|
-
* @param {Selection} [selection] - The current selection
|
|
875
|
-
* @returns {(Annotation & { index: number, node: any })[]} Selected annotation records
|
|
876
916
|
*/
|
|
877
|
-
export function get_selected_annotations(
|
|
917
|
+
export function get_selected_annotations(
|
|
918
|
+
schema: DocumentSchema,
|
|
919
|
+
doc: Document,
|
|
920
|
+
selection?: Selection | null
|
|
921
|
+
): SelectedAttachment[] {
|
|
878
922
|
return get_selected_ranges(schema, doc, selection, 'annotations');
|
|
879
923
|
}
|
|
880
924
|
|
|
881
925
|
/**
|
|
882
|
-
*
|
|
883
|
-
* @returns {Set<string>} Node types represented in the selected ranges
|
|
926
|
+
* Node types represented in the selected ranges.
|
|
884
927
|
*/
|
|
885
|
-
export function get_selected_range_types(selected_ranges) {
|
|
886
|
-
return new Set(
|
|
928
|
+
export function get_selected_range_types(selected_ranges: SelectedAttachment[]): Set<string> {
|
|
929
|
+
return new Set(
|
|
930
|
+
selected_ranges.map(({ node }) => node?.type).filter((type): type is string => Boolean(type))
|
|
931
|
+
);
|
|
887
932
|
}
|
|
888
933
|
|
|
889
934
|
/**
|
|
890
935
|
* Counts references to a node, excluding nodes marked for deletion.
|
|
891
|
-
*
|
|
892
|
-
* @param {DocumentSchema} schema - The document schema
|
|
893
|
-
* @param {Document} doc - The document containing nodes
|
|
894
|
-
* @param {NodeId} target_node_id - The node ID to count references for
|
|
895
|
-
* @param {Record<NodeId, boolean>} nodes_to_delete - Nodes marked for deletion
|
|
896
|
-
* @returns {number} The number of references excluding deleted nodes
|
|
897
936
|
*/
|
|
898
|
-
export function count_references_excluding_deleted(
|
|
937
|
+
export function count_references_excluding_deleted(
|
|
938
|
+
schema: DocumentSchema,
|
|
939
|
+
doc: Document,
|
|
940
|
+
target_node_id: NodeId,
|
|
941
|
+
nodes_to_delete: Record<NodeId, boolean>
|
|
942
|
+
): number {
|
|
899
943
|
let count = 0;
|
|
900
944
|
|
|
901
945
|
for (const node of Object.values(doc.nodes)) {
|
|
@@ -907,7 +951,7 @@ export function count_references_excluding_deleted(schema, doc, target_node_id,
|
|
|
907
951
|
const prop_type = property_type(schema, node.type, property);
|
|
908
952
|
|
|
909
953
|
if (prop_type === 'node_array') {
|
|
910
|
-
count += value.nodes.filter((id) => id === target_node_id).length;
|
|
954
|
+
count += value.nodes.filter((id: NodeId) => id === target_node_id).length;
|
|
911
955
|
} else if (prop_type === 'node' && value === target_node_id) {
|
|
912
956
|
count += 1;
|
|
913
957
|
}
|
|
@@ -929,12 +973,12 @@ export function count_references_excluding_deleted(schema, doc, target_node_id,
|
|
|
929
973
|
* Covers the same reference kinds as count_references_excluding_deleted:
|
|
930
974
|
* a `node` property, `node_array` .nodes ids, and the mark/annotation
|
|
931
975
|
* node_ids carried on `text` and `node_array` properties.
|
|
932
|
-
*
|
|
933
|
-
* @param {DocumentSchema} schema - The document schema
|
|
934
|
-
* @param {any} node - The node whose outgoing references to visit
|
|
935
|
-
* @param {(referenced_id: NodeId) => void} visit - Called per reference occurrence
|
|
936
976
|
*/
|
|
937
|
-
export function visit_node_references(
|
|
977
|
+
export function visit_node_references(
|
|
978
|
+
schema: DocumentSchema,
|
|
979
|
+
node: DocumentNode,
|
|
980
|
+
visit: (referenced_id: NodeId) => void
|
|
981
|
+
): void {
|
|
938
982
|
const node_schema = schema[node.type];
|
|
939
983
|
if (!node_schema) return;
|
|
940
984
|
|
|
@@ -961,14 +1005,9 @@ export function visit_node_references(schema, node, visit) {
|
|
|
961
1005
|
* Builds reference counts for every referenced node in one full-document
|
|
962
1006
|
* scan. Used by cascade deletion so the fixpoint can decrement counts
|
|
963
1007
|
* incrementally instead of re-scanning the document per candidate node.
|
|
964
|
-
*
|
|
965
|
-
* @param {DocumentSchema} schema - The document schema
|
|
966
|
-
* @param {Document} doc - The document containing nodes
|
|
967
|
-
* @returns {Map<NodeId, number>} Reference count per referenced node id
|
|
968
1008
|
*/
|
|
969
|
-
export function build_reference_counts(schema, doc) {
|
|
970
|
-
|
|
971
|
-
const counts = new Map();
|
|
1009
|
+
export function build_reference_counts(schema: DocumentSchema, doc: Document): Map<NodeId, number> {
|
|
1010
|
+
const counts: Map<NodeId, number> = new Map();
|
|
972
1011
|
for (const node of Object.values(doc.nodes)) {
|
|
973
1012
|
visit_node_references(schema, node, (id) => {
|
|
974
1013
|
counts.set(id, (counts.get(id) || 0) + 1);
|
|
@@ -979,15 +1018,14 @@ export function build_reference_counts(schema, doc) {
|
|
|
979
1018
|
|
|
980
1019
|
/**
|
|
981
1020
|
* Gets ids of nodes that reference any of the target nodes.
|
|
982
|
-
*
|
|
983
|
-
* @param {DocumentSchema} schema - The document schema
|
|
984
|
-
* @param {Document} doc - The document containing nodes
|
|
985
|
-
* @param {Iterable<NodeId>} target_node_ids - Node IDs to find referrers for
|
|
986
|
-
* @returns {NodeId[]} IDs of nodes with references to any target node
|
|
987
1021
|
*/
|
|
988
|
-
export function get_referencing_node_ids(
|
|
1022
|
+
export function get_referencing_node_ids(
|
|
1023
|
+
schema: DocumentSchema,
|
|
1024
|
+
doc: Document,
|
|
1025
|
+
target_node_ids: Iterable<NodeId>
|
|
1026
|
+
): NodeId[] {
|
|
989
1027
|
const target_ids = new Set(target_node_ids);
|
|
990
|
-
const referencing_node_ids = new Set();
|
|
1028
|
+
const referencing_node_ids: Set<NodeId> = new Set();
|
|
991
1029
|
if (target_ids.size === 0) return [];
|
|
992
1030
|
|
|
993
1031
|
for (const node of Object.values(doc.nodes)) {
|
|
@@ -997,7 +1035,7 @@ export function get_referencing_node_ids(schema, doc, target_node_ids) {
|
|
|
997
1035
|
const prop_type = property_type(schema, node.type, property);
|
|
998
1036
|
|
|
999
1037
|
if (prop_type === 'node_array') {
|
|
1000
|
-
if (value.nodes.some((id) => target_ids.has(id))) {
|
|
1038
|
+
if (value.nodes.some((id: NodeId) => target_ids.has(id))) {
|
|
1001
1039
|
referencing_node_ids.add(node.id);
|
|
1002
1040
|
}
|
|
1003
1041
|
} else if (prop_type === 'node' && target_ids.has(value)) {
|
|
@@ -1007,9 +1045,7 @@ export function get_referencing_node_ids(schema, doc, target_node_ids) {
|
|
|
1007
1045
|
if (
|
|
1008
1046
|
(prop_type === 'text' || prop_type === 'node_array') &&
|
|
1009
1047
|
value &&
|
|
1010
|
-
[...value.marks, ...value.annotations].some((range) =>
|
|
1011
|
-
target_ids.has(range.node_id)
|
|
1012
|
-
)
|
|
1048
|
+
[...value.marks, ...value.annotations].some((range) => target_ids.has(range.node_id))
|
|
1013
1049
|
) {
|
|
1014
1050
|
referencing_node_ids.add(node.id);
|
|
1015
1051
|
}
|
|
@@ -1023,11 +1059,16 @@ export function get_referencing_node_ids(schema, doc, target_node_ids) {
|
|
|
1023
1059
|
* Validates a selection against the current document state.
|
|
1024
1060
|
* Works with any object that implements get() and inspect() (Session or Transaction).
|
|
1025
1061
|
*
|
|
1026
|
-
* @param {Selection} selection - The selection to validate
|
|
1027
|
-
* @param {{ get: Function, inspect: Function }} session_or_transaction - A Session or Transaction instance
|
|
1028
1062
|
* @throws {Error} Throws if the selection is invalid
|
|
1029
1063
|
*/
|
|
1030
|
-
export function validate_selection(
|
|
1064
|
+
export function validate_selection(
|
|
1065
|
+
selection: Selection | null | undefined,
|
|
1066
|
+
session_or_transaction: {
|
|
1067
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1068
|
+
get: (path: DocumentPath) => any;
|
|
1069
|
+
inspect: (path: DocumentPath) => Inspection;
|
|
1070
|
+
}
|
|
1071
|
+
): void {
|
|
1031
1072
|
if (!selection) return;
|
|
1032
1073
|
|
|
1033
1074
|
const selection_type = selection.type;
|