svedit 0.3.0 → 0.4.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 +39 -21
- package/dist/{AnnotatedStringProperty.svelte → AnnotatedTextProperty.svelte} +28 -40
- package/dist/AnnotatedTextProperty.svelte.d.ts +7 -0
- package/dist/AnnotatedTextProperty.svelte.d.ts.map +1 -0
- package/dist/Document.svelte.d.ts +39 -16
- package/dist/Document.svelte.d.ts.map +1 -1
- package/dist/Document.svelte.js +192 -80
- package/dist/Svedit.svelte +149 -97
- package/dist/Svedit.svelte.d.ts.map +1 -1
- package/dist/Transaction.svelte.d.ts +4 -4
- package/dist/Transaction.svelte.d.ts.map +1 -1
- package/dist/Transaction.svelte.js +60 -77
- package/dist/commands.svelte.d.ts.map +1 -1
- package/dist/commands.svelte.js +18 -10
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/types.d.ts +66 -21
- package/dist/util.d.ts +16 -16
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +33 -34
- package/package.json +1 -1
- package/src/lib/{AnnotatedStringProperty.svelte → AnnotatedTextProperty.svelte} +28 -40
- package/src/lib/Document.svelte.js +192 -80
- package/src/lib/Svedit.svelte +149 -97
- package/src/lib/Transaction.svelte.js +60 -77
- package/src/lib/commands.svelte.js +18 -10
- package/src/lib/index.js +1 -1
- package/src/lib/types.d.ts +66 -21
- package/src/lib/util.js +33 -34
- package/dist/AnnotatedStringProperty.svelte.d.ts +0 -7
- package/dist/AnnotatedStringProperty.svelte.d.ts.map +0 -1
package/dist/Document.svelte.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Transaction from './Transaction.svelte.js';
|
|
2
|
-
import { char_slice } from './util.js';
|
|
2
|
+
import { char_slice, get_char_length } from './util.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @import {
|
|
@@ -11,6 +11,10 @@ import { char_slice } from './util.js';
|
|
|
11
11
|
* DocumentSchemaPrimitive,
|
|
12
12
|
* NodeProperty,
|
|
13
13
|
* NodeArrayProperty,
|
|
14
|
+
* NodeKind,
|
|
15
|
+
* NodeSchema,
|
|
16
|
+
* TextNodeSchema,
|
|
17
|
+
* NonTextNodeSchema,
|
|
14
18
|
* DocumentSchema,
|
|
15
19
|
* SerializedDocument
|
|
16
20
|
* } from './types.d.ts';
|
|
@@ -20,7 +24,7 @@ import { char_slice } from './util.js';
|
|
|
20
24
|
* Identity function — keeps schema at runtime & makes IDE infer types.
|
|
21
25
|
* Similar to your define_schema pattern but for document schemas.
|
|
22
26
|
*
|
|
23
|
-
* @template {Record<string,
|
|
27
|
+
* @template {Record<string, NodeSchema>} S
|
|
24
28
|
* @param {S} schema - The document schema to validate
|
|
25
29
|
* @returns {S} The same schema, but with type information preserved
|
|
26
30
|
*/
|
|
@@ -36,7 +40,7 @@ export function define_document_schema(schema) {
|
|
|
36
40
|
export function is_primitive_type(type) {
|
|
37
41
|
return [
|
|
38
42
|
'string', 'number', 'boolean', 'integer', 'datetime',
|
|
39
|
-
'
|
|
43
|
+
'annotated_text', 'string_array', 'number_array',
|
|
40
44
|
'boolean_array', 'integer_array'
|
|
41
45
|
].includes(type);
|
|
42
46
|
}
|
|
@@ -63,7 +67,7 @@ export function get_default_node_type(property_definition) {
|
|
|
63
67
|
export function validate_document_schema(document_schema) {
|
|
64
68
|
// Check that all referenced node types exist
|
|
65
69
|
for (const [node_type, node_schema] of Object.entries(document_schema)) {
|
|
66
|
-
for (const [prop_name, prop_def] of Object.entries(node_schema)) {
|
|
70
|
+
for (const [prop_name, prop_def] of Object.entries(node_schema.properties)) {
|
|
67
71
|
if (prop_def.type === 'node' || prop_def.type === 'node_array') {
|
|
68
72
|
const missing_types = prop_def.node_types.filter(ref_type => !(ref_type in document_schema));
|
|
69
73
|
if (missing_types.length > 0) {
|
|
@@ -92,11 +96,11 @@ function validate_primitive_value(type, value) {
|
|
|
92
96
|
return Number.isInteger(value);
|
|
93
97
|
case 'datetime':
|
|
94
98
|
return typeof value === 'string' && !isNaN(Date.parse(value));
|
|
95
|
-
case '
|
|
96
|
-
return
|
|
97
|
-
value
|
|
98
|
-
typeof value
|
|
99
|
-
Array.isArray(value
|
|
99
|
+
case 'annotated_text':
|
|
100
|
+
return typeof value === 'object' &&
|
|
101
|
+
value !== null &&
|
|
102
|
+
typeof value.text === 'string' &&
|
|
103
|
+
Array.isArray(value.annotations);
|
|
100
104
|
case 'string_array':
|
|
101
105
|
return Array.isArray(value) && value.every(v => typeof v === 'string');
|
|
102
106
|
case 'number_array':
|
|
@@ -136,7 +140,7 @@ function validate_node(node, schema, all_nodes = {}) {
|
|
|
136
140
|
|
|
137
141
|
const node_schema = schema[node.type];
|
|
138
142
|
|
|
139
|
-
for (const [prop_name, prop_def] of Object.entries(node_schema)) {
|
|
143
|
+
for (const [prop_name, prop_def] of Object.entries(node_schema.properties)) {
|
|
140
144
|
const value = node[prop_name];
|
|
141
145
|
|
|
142
146
|
// Check primitive types
|
|
@@ -177,7 +181,7 @@ function validate_node(node, schema, all_nodes = {}) {
|
|
|
177
181
|
|
|
178
182
|
export default class Document {
|
|
179
183
|
/** @type {Selection | undefined} */
|
|
180
|
-
selection = $state();
|
|
184
|
+
#selection = $state();
|
|
181
185
|
config = $state();
|
|
182
186
|
document_id = $state();
|
|
183
187
|
nodes = $state();
|
|
@@ -190,6 +194,86 @@ export default class Document {
|
|
|
190
194
|
|
|
191
195
|
// Reactive variable for selected node
|
|
192
196
|
selected_node = $derived(this.get_selected_node());
|
|
197
|
+
layout_node = $derived(this.get_layout_node());
|
|
198
|
+
available_annotation_types = $derived(this.get_available_annotation_types());
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Gets the current selection
|
|
202
|
+
* @returns {Selection | undefined}
|
|
203
|
+
*/
|
|
204
|
+
get selection() {
|
|
205
|
+
return this.#selection;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Sets the selection with validation
|
|
210
|
+
* @param {Selection | undefined} value - The new selection
|
|
211
|
+
* @throws {Error} Throws if the selection is invalid
|
|
212
|
+
*/
|
|
213
|
+
set selection(value) {
|
|
214
|
+
// if (value !== undefined) {
|
|
215
|
+
// this._validate_selection(value);
|
|
216
|
+
// }
|
|
217
|
+
this.#selection = value;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Validates that a selection is within bounds and refers to valid paths.
|
|
222
|
+
*
|
|
223
|
+
* @param {Selection} selection - The selection to validate
|
|
224
|
+
* @throws {Error} Throws if the selection is invalid
|
|
225
|
+
* @private
|
|
226
|
+
*/
|
|
227
|
+
_validate_selection(selection) {
|
|
228
|
+
if (!selection) return; // no selection is a valid selection
|
|
229
|
+
if (selection && !Array.isArray(selection.path)) {
|
|
230
|
+
throw new Error('Selection must have a valid path');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const selection_type = selection.type;
|
|
234
|
+
if (selection_type === 'node') {
|
|
235
|
+
// For node selections, path should point to a node_array
|
|
236
|
+
if (this.inspect(selection.path).type !== 'node_array') {
|
|
237
|
+
throw new Error(`Node selection path does not point to a node array: ${selection.path.join('.')}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const node_array = this.get(selection.path);
|
|
241
|
+
|
|
242
|
+
// Validate anchor_offset and focus_offset are within bounds
|
|
243
|
+
const max_offset = node_array.length;
|
|
244
|
+
|
|
245
|
+
if (selection.anchor_offset < 0 || selection.anchor_offset > max_offset) {
|
|
246
|
+
throw new Error(`Node selection anchor_offset ${selection.anchor_offset} is out of bounds (0-${max_offset})`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (selection.focus_offset < 0 || selection.focus_offset > max_offset) {
|
|
250
|
+
throw new Error(`Node selection focus_offset ${selection.focus_offset} is out of bounds (0-${max_offset})`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
} else if (selection_type === 'text') {
|
|
254
|
+
// For text selections, path should point to an annotated_text property
|
|
255
|
+
const annotated_text = this.get(selection.path);
|
|
256
|
+
|
|
257
|
+
// Validate anchor_offset and focus_offset are within text bounds
|
|
258
|
+
const char_length = get_char_length(annotated_text.text);
|
|
259
|
+
|
|
260
|
+
if (selection.anchor_offset < 0 || selection.anchor_offset > char_length) {
|
|
261
|
+
throw new Error(`Text selection anchor_offset ${selection.anchor_offset} is out of bounds (0-${char_length})`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (selection.focus_offset < 0 || selection.focus_offset > char_length) {
|
|
265
|
+
throw new Error(`Text selection focus_offset ${selection.focus_offset} is out of bounds (0-${char_length})`);
|
|
266
|
+
}
|
|
267
|
+
} else if (selection_type === 'property') {
|
|
268
|
+
// For property selections, just validate the path exists
|
|
269
|
+
if (!this.inspect(selection.path)) {
|
|
270
|
+
throw new Error(`Property selection path not found: ${selection.path.join('.')}`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
} else {
|
|
274
|
+
throw new Error(`Unknown selection type: ${selection_type}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
193
277
|
|
|
194
278
|
/**
|
|
195
279
|
* @param {DocumentSchema} schema - The document schema
|
|
@@ -203,7 +287,6 @@ export default class Document {
|
|
|
203
287
|
validate_document_schema(schema);
|
|
204
288
|
|
|
205
289
|
this.schema = schema;
|
|
206
|
-
this.selection = selection;
|
|
207
290
|
this.config = config;
|
|
208
291
|
this.nodes = {};
|
|
209
292
|
|
|
@@ -216,6 +299,9 @@ export default class Document {
|
|
|
216
299
|
|
|
217
300
|
// The last element in the serialized_doc is the document itself (the root node)
|
|
218
301
|
this.document_id = serialized_doc.at(-1)?.id;
|
|
302
|
+
|
|
303
|
+
// Set selection after nodes are initialized so validation can work properly
|
|
304
|
+
this.selection = selection;
|
|
219
305
|
}
|
|
220
306
|
|
|
221
307
|
generate_id() {
|
|
@@ -226,6 +312,13 @@ export default class Document {
|
|
|
226
312
|
}
|
|
227
313
|
}
|
|
228
314
|
|
|
315
|
+
get_available_annotation_types() {
|
|
316
|
+
if (this.selection?.type !== 'text') return [];
|
|
317
|
+
const path = this.selection.path;
|
|
318
|
+
const property_schema = this.inspect(path);
|
|
319
|
+
return property_schema.node_types || [];
|
|
320
|
+
}
|
|
321
|
+
|
|
229
322
|
// Helper function to get the currently selected node
|
|
230
323
|
get_selected_node() {
|
|
231
324
|
if (!this.selection) return null;
|
|
@@ -240,13 +333,37 @@ export default class Document {
|
|
|
240
333
|
return node_id ? this.get(node_id) : null;
|
|
241
334
|
} else {
|
|
242
335
|
// we are assuming we are either in a text or property (=custom) selection
|
|
243
|
-
const
|
|
244
|
-
if (!
|
|
245
|
-
const owner_node = this.get(
|
|
336
|
+
const owner_node_path = this.selection?.path?.slice(0, -1);
|
|
337
|
+
if (!owner_node_path) return null;
|
|
338
|
+
const owner_node = this.get(owner_node_path);
|
|
246
339
|
return owner_node;
|
|
247
340
|
}
|
|
248
341
|
}
|
|
249
342
|
|
|
343
|
+
// NOTE: This code is a bit whacky, but works for now.
|
|
344
|
+
// TODO: Refactor as we settle on a final API
|
|
345
|
+
get_layout_node() {
|
|
346
|
+
if (!this.selected_node) return null;
|
|
347
|
+
|
|
348
|
+
// The selected node already is a layout node
|
|
349
|
+
if (this.selected_node.layout) {
|
|
350
|
+
return this.selected_node;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// We resolve the parent node if available, and return it if it's a layout node.
|
|
354
|
+
// NOTE: We only support one level atm, we may want to implement this recursively
|
|
355
|
+
if (this.selection.type === 'node') {
|
|
356
|
+
const parent_node = this.get(this.selection.path.slice(0, -1));
|
|
357
|
+
return parent_node.layout ? parent_node : null;
|
|
358
|
+
} else {
|
|
359
|
+
// We are either in a text or property (=custom) selection
|
|
360
|
+
const parent_node_path = this.selection?.path?.slice(0, -3);
|
|
361
|
+
if (!parent_node_path) return null;
|
|
362
|
+
const parent_node = this.get(parent_node_path);
|
|
363
|
+
return parent_node.layout ? parent_node : null;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
250
367
|
// Internal unsafe function: Never call this directly
|
|
251
368
|
_apply_op(op) {
|
|
252
369
|
const [type, ...args] = op;
|
|
@@ -352,8 +469,8 @@ export default class Document {
|
|
|
352
469
|
* doc.get(['page_1', 'body', 3, 'list_items', 0]) // => { type: 'list_item', id: 'list_item_1', ... }
|
|
353
470
|
*
|
|
354
471
|
* @example
|
|
355
|
-
* // Get an annotated
|
|
356
|
-
* doc.get(['page_1', 'cover', 'title']) // =>
|
|
472
|
+
* // Get an annotated text property
|
|
473
|
+
* doc.get(['page_1', 'cover', 'title']) // => {text: 'Hello world', annotations: []}
|
|
357
474
|
*/
|
|
358
475
|
get(path) {
|
|
359
476
|
if (typeof path === 'string') {
|
|
@@ -372,6 +489,9 @@ export default class Document {
|
|
|
372
489
|
if (this.property_type(val.type, path_segment) === 'node_array') {
|
|
373
490
|
val = val[path_segment]; // e.g. for the page body ['list_1', 'paragraph_1']
|
|
374
491
|
val_type = 'node_array';
|
|
492
|
+
} else if (this.property_type(val.type, path_segment) === 'annotated_text') {
|
|
493
|
+
val = val[path_segment];
|
|
494
|
+
val_type = 'annotated_text';
|
|
375
495
|
} else if (this.property_type(val.type, path_segment) === 'node') {
|
|
376
496
|
val = this.nodes[val[path_segment]];
|
|
377
497
|
val_type = 'node';
|
|
@@ -379,7 +499,7 @@ export default class Document {
|
|
|
379
499
|
val = val[path_segment];
|
|
380
500
|
val_type = 'value_array';
|
|
381
501
|
} else {
|
|
382
|
-
// Resolve value properties
|
|
502
|
+
// Resolve value properties such as string, integer, datetime
|
|
383
503
|
val = val[path_segment];
|
|
384
504
|
val_type = 'value';
|
|
385
505
|
}
|
|
@@ -390,6 +510,32 @@ export default class Document {
|
|
|
390
510
|
} else if (val_type === 'value_array') {
|
|
391
511
|
val = val[path_segment];
|
|
392
512
|
val_type = 'value';
|
|
513
|
+
} else if (val_type === 'annotated_text') {
|
|
514
|
+
if (path_segment === 'text') {
|
|
515
|
+
val = val.text;
|
|
516
|
+
val_type = 'value';
|
|
517
|
+
} else if (path_segment === 'annotations') {
|
|
518
|
+
val = val.annotations;
|
|
519
|
+
val_type = 'annotation_array';
|
|
520
|
+
} else {
|
|
521
|
+
throw new Error(`Invalid path segment "${path_segment}" for annotated_text. Use "text" or "annotations".`);
|
|
522
|
+
}
|
|
523
|
+
} else if (val_type === 'annotation_array') {
|
|
524
|
+
val = val[path_segment];
|
|
525
|
+
val_type = 'annotation';
|
|
526
|
+
} else if (val_type === 'annotation') {
|
|
527
|
+
if (path_segment === 'node_id') {
|
|
528
|
+
val = this.nodes[val.node_id];
|
|
529
|
+
val_type = 'node';
|
|
530
|
+
} else if (path_segment === 'start_offset') {
|
|
531
|
+
val = val.start_offset;
|
|
532
|
+
val_type = 'value';
|
|
533
|
+
} else if (path_segment === 'end_offset') {
|
|
534
|
+
val = val.end_offset;
|
|
535
|
+
val_type = 'value';
|
|
536
|
+
} else {
|
|
537
|
+
throw new Error(`Invalid path segment "${path_segment}" for annotation. Use "start_offset", "end_offset", or "node_id".`);
|
|
538
|
+
}
|
|
393
539
|
}
|
|
394
540
|
}
|
|
395
541
|
return val;
|
|
@@ -430,7 +576,7 @@ export default class Document {
|
|
|
430
576
|
kind: 'property',
|
|
431
577
|
name: property_name,
|
|
432
578
|
// Merge property spec from schema
|
|
433
|
-
...this.schema[parent.type][property_name]
|
|
579
|
+
...this.schema[parent.type].properties[property_name]
|
|
434
580
|
};
|
|
435
581
|
} else {
|
|
436
582
|
// Parent is a property (or we are at the root), so we are dealing with a node.
|
|
@@ -445,17 +591,13 @@ export default class Document {
|
|
|
445
591
|
}
|
|
446
592
|
|
|
447
593
|
/**
|
|
448
|
-
* Determines the kind of a node ('
|
|
449
|
-
*
|
|
594
|
+
* Determines the kind of a node ('block' for structured blocks, 'text' for pure
|
|
595
|
+
* text nodes or 'annotation' for annotation nodes.
|
|
450
596
|
* @param {any} node
|
|
451
|
-
* @returns {
|
|
597
|
+
* @returns {NodeKind}
|
|
452
598
|
*/
|
|
453
599
|
kind(node) {
|
|
454
|
-
|
|
455
|
-
return 'text'
|
|
456
|
-
} else {
|
|
457
|
-
return 'node';
|
|
458
|
-
}
|
|
600
|
+
return this.schema[node.type].kind;
|
|
459
601
|
}
|
|
460
602
|
|
|
461
603
|
/**
|
|
@@ -469,17 +611,18 @@ export default class Document {
|
|
|
469
611
|
if (this.selection?.type !== 'text') return null;
|
|
470
612
|
|
|
471
613
|
const { start, end } = this.get_selection_range();
|
|
472
|
-
const
|
|
473
|
-
const annotations =
|
|
614
|
+
const annotated_text = this.get(this.selection.path);
|
|
615
|
+
const annotations = annotated_text.annotations;
|
|
474
616
|
|
|
475
|
-
const active_annotation = annotations.find((
|
|
476
|
-
(
|
|
477
|
-
(
|
|
478
|
-
(
|
|
617
|
+
const active_annotation = annotations.find(({start_offset, end_offset}) =>
|
|
618
|
+
(start_offset <= start && end_offset > start) ||
|
|
619
|
+
(start_offset < end && end_offset >= end) ||
|
|
620
|
+
(start_offset >= start && end_offset <= end)
|
|
479
621
|
) || null;
|
|
480
622
|
|
|
481
|
-
if (annotation_type) {
|
|
482
|
-
|
|
623
|
+
if (annotation_type && active_annotation) {
|
|
624
|
+
const annotation_node = this.get(active_annotation?.node_id);
|
|
625
|
+
return annotation_node?.type === annotation_type ? active_annotation : null;
|
|
483
626
|
} else {
|
|
484
627
|
return active_annotation;
|
|
485
628
|
}
|
|
@@ -491,8 +634,8 @@ export default class Document {
|
|
|
491
634
|
|
|
492
635
|
const start = Math.min(this.selection.anchor_offset, this.selection.focus_offset);
|
|
493
636
|
const end = Math.max(this.selection.anchor_offset, this.selection.focus_offset);
|
|
494
|
-
const
|
|
495
|
-
return char_slice(
|
|
637
|
+
const annotated_text = this.get(this.selection.path);
|
|
638
|
+
return char_slice(annotated_text.text, start, end);
|
|
496
639
|
}
|
|
497
640
|
|
|
498
641
|
get_selected_nodes() {
|
|
@@ -504,41 +647,6 @@ export default class Document {
|
|
|
504
647
|
return $state.snapshot(node_array.slice(start, end));
|
|
505
648
|
}
|
|
506
649
|
|
|
507
|
-
move_node_cursor(direction) {
|
|
508
|
-
if (this.selection?.type !== 'node') return;
|
|
509
|
-
const node_array = this.get(this.selection.path);
|
|
510
|
-
|
|
511
|
-
const { start, end } = this.get_selection_range();
|
|
512
|
-
|
|
513
|
-
if (this.selection.anchor_offset !== this.selection.focus_offset) {
|
|
514
|
-
// If selection is not collapsed, collapse it to the right or the left
|
|
515
|
-
if (direction === 'forward') {
|
|
516
|
-
this.selection.focus_offset = end;
|
|
517
|
-
this.selection.anchor_offset = end;
|
|
518
|
-
} else if (direction === 'backward') {
|
|
519
|
-
this.selection.focus_offset = start;
|
|
520
|
-
this.selection.anchor_offset = start;
|
|
521
|
-
}
|
|
522
|
-
} else if (direction === 'forward' && end < node_array.length) {
|
|
523
|
-
this.selection.focus_offset = end + 1;
|
|
524
|
-
this.selection.anchor_offset = end + 1;
|
|
525
|
-
} else if (direction === 'backward' && start > 0) {
|
|
526
|
-
this.selection.focus_offset = start - 1;
|
|
527
|
-
this.selection.anchor_offset = start - 1;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
expand_node_selection(direction) {
|
|
532
|
-
if (this.selection.type !== 'node') return;
|
|
533
|
-
const node_array = this.get(this.selection.path);
|
|
534
|
-
|
|
535
|
-
if (direction === 'forward') {
|
|
536
|
-
this.selection.focus_offset = Math.min(this.selection.focus_offset + 1, node_array.length);
|
|
537
|
-
} else if (direction === 'backward') {
|
|
538
|
-
this.selection.focus_offset = Math.max(this.selection.focus_offset - 1, 0);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
650
|
select_parent() {
|
|
543
651
|
if (!this.selection) return;
|
|
544
652
|
if (['text', 'property'].includes(this.selection?.type)) {
|
|
@@ -602,17 +710,21 @@ export default class Document {
|
|
|
602
710
|
return;
|
|
603
711
|
}
|
|
604
712
|
visited[node.id] = true;
|
|
605
|
-
for (const [, value] of Object.entries(node)) {
|
|
606
|
-
|
|
607
|
-
|
|
713
|
+
for (const [property_name, value] of Object.entries(node)) {
|
|
714
|
+
const property_schema = this.schema[node.type].properties[property_name];
|
|
715
|
+
|
|
716
|
+
if (property_schema?.type === 'node_array') {
|
|
608
717
|
for (const v of value) {
|
|
609
718
|
if (typeof v === 'string') {
|
|
610
719
|
visit($state.snapshot(this.get(v)));
|
|
611
720
|
}
|
|
612
721
|
}
|
|
613
|
-
} else if (
|
|
614
|
-
// TODO: Use schema inspection and do this only for properties of type `ref`
|
|
722
|
+
} else if (property_schema?.type === 'node') {
|
|
615
723
|
visit($state.snapshot(this.get(value)));
|
|
724
|
+
} else if (property_schema?.type === 'annotated_text') {
|
|
725
|
+
for (const annotation of value.annotations) {
|
|
726
|
+
visit($state.snapshot(this.get(annotation.node_id)));
|
|
727
|
+
}
|
|
616
728
|
}
|
|
617
729
|
}
|
|
618
730
|
// Finally add the node to the result.
|
|
@@ -633,7 +745,7 @@ export default class Document {
|
|
|
633
745
|
}
|
|
634
746
|
|
|
635
747
|
// property_type('page', 'body') => 'node_array'
|
|
636
|
-
// property_type('paragraph', 'content') => '
|
|
748
|
+
// property_type('paragraph', 'content') => 'annotated_text'
|
|
637
749
|
property_type(type, property) {
|
|
638
750
|
if (typeof type !== 'string') throw new Error(`Invalid type ${type} provided`);
|
|
639
751
|
if (typeof property !== 'string') throw new Error(`Invalid property ${property} provided`);
|
|
@@ -642,9 +754,9 @@ export default class Document {
|
|
|
642
754
|
if (property === 'id') return 'string';
|
|
643
755
|
|
|
644
756
|
if (!this.schema[type]) throw new Error(`Type ${type} not found in schema`);
|
|
645
|
-
if (!this.schema[type][property]) throw new Error(`Property ${property} not found in type ${type}`);
|
|
757
|
+
if (!this.schema[type].properties[property]) throw new Error(`Property ${property} not found in type ${type}`);
|
|
646
758
|
|
|
647
|
-
return this.schema[type][property].type;
|
|
759
|
+
return this.schema[type].properties[property].type;
|
|
648
760
|
}
|
|
649
761
|
|
|
650
762
|
// Count how many times a node is referenced in the document
|