svedit 0.8.0 → 0.10.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 +137 -4
- package/dist/AnnotatedTextProperty.svelte +41 -8
- package/dist/AnnotatedTextProperty.svelte.d.ts.map +1 -1
- package/dist/Command.svelte.d.ts +0 -1
- package/dist/Command.svelte.d.ts.map +1 -1
- package/dist/Command.svelte.js +18 -3
- package/dist/CustomProperty.svelte +20 -9
- package/dist/CustomProperty.svelte.d.ts +2 -1
- package/dist/CustomProperty.svelte.d.ts.map +1 -1
- package/dist/Node.svelte +7 -5
- package/dist/Node.svelte.d.ts.map +1 -1
- package/dist/NodeArrayProperty.svelte +65 -42
- package/dist/NodeArrayProperty.svelte.d.ts.map +1 -1
- package/dist/NodeCaret.svelte +0 -7
- package/dist/NodeGap.svelte +179 -81
- package/dist/NodeGap.svelte.d.ts +0 -2
- package/dist/NodeGap.svelte.d.ts.map +1 -1
- package/dist/NodeGapMarkers.svelte +251 -84
- package/dist/NodeGapMarkers.svelte.d.ts +2 -0
- package/dist/NodeGapMarkers.svelte.d.ts.map +1 -1
- package/dist/NodeSelectionMarkers.svelte +4 -3
- package/dist/NodeSelectionMarkers.svelte.d.ts.map +1 -1
- package/dist/Session.svelte.d.ts +12 -1
- package/dist/Session.svelte.d.ts.map +1 -1
- package/dist/Session.svelte.js +40 -3
- package/dist/Svedit.svelte +340 -147
- package/dist/Svedit.svelte.d.ts.map +1 -1
- package/dist/Transaction.svelte.d.ts +24 -5
- package/dist/Transaction.svelte.d.ts.map +1 -1
- package/dist/Transaction.svelte.js +103 -61
- package/dist/doc_utils.d.ts +64 -3
- package/dist/doc_utils.d.ts.map +1 -1
- package/dist/doc_utils.js +276 -10
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -1
- package/dist/node_visibility.svelte.d.ts +9 -0
- package/dist/node_visibility.svelte.d.ts.map +1 -0
- package/dist/node_visibility.svelte.js +718 -0
- package/dist/styles/svedit-animations.css +8 -0
- package/dist/styles/svedit-colors.css +1 -0
- package/dist/types.d.ts +46 -3
- package/dist/utils.d.ts +52 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +109 -3
- package/package.json +3 -2
- package/src/lib/AnnotatedTextProperty.svelte +41 -8
- package/src/lib/Command.svelte.js +18 -3
- package/src/lib/CustomProperty.svelte +20 -9
- package/src/lib/Node.svelte +7 -5
- package/src/lib/NodeArrayProperty.svelte +65 -42
- package/src/lib/NodeCaret.svelte +0 -7
- package/src/lib/NodeGap.svelte +179 -81
- package/src/lib/NodeGapMarkers.svelte +251 -84
- package/src/lib/NodeSelectionMarkers.svelte +4 -3
- package/src/lib/Session.svelte.js +40 -3
- package/src/lib/Svedit.svelte +340 -147
- package/src/lib/Transaction.svelte.js +103 -61
- package/src/lib/doc_utils.js +276 -10
- package/src/lib/index.js +5 -1
- package/src/lib/node_visibility.svelte.js +718 -0
- package/src/lib/styles/svedit-animations.css +8 -0
- package/src/lib/styles/svedit-colors.css +1 -0
- package/src/lib/types.d.ts +46 -3
- package/src/lib/utils.js +109 -3
- package/dist/node_gap_computation.svelte.d.ts +0 -5
- package/dist/node_gap_computation.svelte.d.ts.map +0 -1
- package/dist/node_gap_computation.svelte.js +0 -621
- package/src/lib/node_gap_computation.svelte.js +0 -621
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Svedit.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Svedit.svelte.js"],"names":[],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"Svedit.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Svedit.svelte.js"],"names":[],"mappings":";;;;;;;AAigDA;;eAAqD;iCAh+C1C,cAAc"}
|
|
@@ -34,6 +34,12 @@ export default class Transaction {
|
|
|
34
34
|
/** @type {Record<string, any>[]} */
|
|
35
35
|
inverse_ops: Record<string, any>[];
|
|
36
36
|
selection_before: Selection;
|
|
37
|
+
/** @type {NodeId[]} */
|
|
38
|
+
created_node_ids: NodeId[];
|
|
39
|
+
/** @type {NodeId[]} */
|
|
40
|
+
modified_node_ids: NodeId[];
|
|
41
|
+
/** @type {NodeId[]} */
|
|
42
|
+
deleted_node_ids: NodeId[];
|
|
37
43
|
/**
|
|
38
44
|
* Gets a value from the document at the specified path.
|
|
39
45
|
*
|
|
@@ -107,6 +113,12 @@ export default class Transaction {
|
|
|
107
113
|
* @private
|
|
108
114
|
*/
|
|
109
115
|
private _apply_op;
|
|
116
|
+
/**
|
|
117
|
+
* @param {NodeId[]} node_ids
|
|
118
|
+
* @param {NodeId} node_id
|
|
119
|
+
* @private
|
|
120
|
+
*/
|
|
121
|
+
private _track_node_id;
|
|
110
122
|
/**
|
|
111
123
|
* Sets a property of a node to a new value.
|
|
112
124
|
*
|
|
@@ -129,9 +141,9 @@ export default class Transaction {
|
|
|
129
141
|
* Creates a new node in the document.
|
|
130
142
|
*
|
|
131
143
|
* The node must have a valid id and must not already exist in the document.
|
|
132
|
-
*
|
|
144
|
+
* Omitted properties with schema defaults are filled before validation.
|
|
133
145
|
*
|
|
134
|
-
* @param {any} node - The node object to create (must include id, type, and
|
|
146
|
+
* @param {any} node - The node object to create (must include id, type, and required properties)
|
|
135
147
|
* @returns {Transaction} This transaction instance for method chaining
|
|
136
148
|
* @throws {Error} If the node ID is invalid or if the node already exists
|
|
137
149
|
*
|
|
@@ -150,6 +162,13 @@ export default class Transaction {
|
|
|
150
162
|
*
|
|
151
163
|
* The node's current state is captured for undo support before deletion.
|
|
152
164
|
*
|
|
165
|
+
* NOTE: This is a force-delete and intentionally does NOT check whether
|
|
166
|
+
* the node is still referenced from elsewhere — callers (e.g.
|
|
167
|
+
* `annotate_text`) sometimes need to remove a node while a stale
|
|
168
|
+
* reference to it still exists in the property they are about to update.
|
|
169
|
+
* For ref-count-aware cleanup, see how `set` calls
|
|
170
|
+
* `_cascade_delete_unreferenced_nodes`.
|
|
171
|
+
*
|
|
153
172
|
* @param {any} id - The ID of the node to delete
|
|
154
173
|
* @returns {Transaction} This transaction instance for method chaining
|
|
155
174
|
*
|
|
@@ -197,13 +216,13 @@ export default class Transaction {
|
|
|
197
216
|
*/
|
|
198
217
|
annotate_text(annotation_type: any, annotation_properties: any): Transaction;
|
|
199
218
|
/**
|
|
200
|
-
* Deletes the currently selected text or
|
|
219
|
+
* Deletes the currently selected text, node, or property.
|
|
201
220
|
*
|
|
202
221
|
* Behavior depends on selection type:
|
|
203
222
|
* - For node selections: Removes selected nodes and cascades deletion of unreferenced nodes
|
|
204
223
|
* - For text selections: Removes selected text and adjusts annotations accordingly
|
|
205
224
|
* - For collapsed selections: Deletes the previous character/node (backward) or next character/node (forward)
|
|
206
|
-
* -
|
|
225
|
+
* - For property selections: Delegates to the config's handle_property_deletion hook
|
|
207
226
|
*
|
|
208
227
|
* @param {'backward' | 'forward'} [direction] - Direction of deletion for collapsed selections
|
|
209
228
|
* @returns {Transaction} This transaction instance for method chaining
|
|
@@ -259,8 +278,8 @@ export default class Transaction {
|
|
|
259
278
|
}
|
|
260
279
|
import type { DocumentSchema } from './types.d.js';
|
|
261
280
|
import type { Selection } from './types.d.js';
|
|
262
|
-
import type { DocumentPath } from './types.d.js';
|
|
263
281
|
import type { NodeId } from './types.d.js';
|
|
282
|
+
import type { DocumentPath } from './types.d.js';
|
|
264
283
|
import type { Annotation } from './types.d.js';
|
|
265
284
|
import type { Document } from './types.d.js';
|
|
266
285
|
//# sourceMappingURL=Transaction.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Transaction.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Transaction.svelte.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Transaction.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Transaction.svelte.js"],"names":[],"mappings":"AA0BA;;;;;;;;;;;;;;GAcG;AACH;IACC;;;;;;;OAOG;IACH,oBALW,cAAc,OACd,QAAQ,aACR,SAAS,GAAG,IAAI,UAChB,MAAM,EAuBhB;IApBA,uBAAoB;IACpB,qBAAqB;IACrB,KADW,MAAM,CACH;IACd,wBAAwB;IACxB,WADW,SAAS,CACM;IAC1B,YAAoB;IAEpB,oCAAoC;IACpC,KADW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CACnB;IACb,oCAAoC;IACpC,aADW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CACX;IAErB,4BAAiC;IAEjC,uBAAuB;IACvB,kBADW,MAAM,EAAE,CACO;IAC1B,uBAAuB;IACvB,mBADW,MAAM,EAAE,CACQ;IAC3B,uBAAuB;IACvB,kBADW,MAAM,EAAE,CACO;IAG3B;;;;;OAKG;IACH,UAHW,YAAY,GAAC,MAAM,GACjB,GAAG,CAIf;IAED;;;;;;OAMG;IACH,oBAJW,MAAM,YACN,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;OAKG;IACH,WAHW,GAAG,GACD,OAAO,GAAC,MAAM,GAAC,YAAY,CAIvC;IAED;;;;;OAKG;IACH,cAHW,YAAY,GACV;QAAC,IAAI,EAAE,UAAU,GAAC,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAC,CAIzD;IAED;;;;OAIG;IACH,eAFa,MAAM,CAIlB;IAED;;;;;OAKG;IACH,oBAHW,GAAG,QAKb;IAGD;;;;;OAKG;IACH,8BAHW,MAAM,GACJ,MAAM,EAAE,CAKpB;IAED;;;;OAIG;IACH,kCAFa,MAAM,EAAE,CAOpB;IAED;;;;;;OAMG;IACH,oCAHW,MAAM,GACJ,UAAU,GAAC,IAAI,CAI3B;IAED;;;;;OAKG;IACH,kBAEC;IAED;;;;OAIG;IACH,uBAEC;IAED;;;;;;;;;;;;;;;OAeG;IACH,UAVW,YAAY,SACZ,GAAG,GACD,WAAW,CA8DvB;IAKD,qCAkCC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACH,aAbW,GAAG,GACD,WAAW,CA4BvB;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,WARW,GAAG,GACD,WAAW,CAuBvB;IAED;;;;;;OAMG;IACH,yBAJW,SAAS,GACP,WAAW,CAOvB;IAED;;;;;;OAMG;IACH,4BAEC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,+BAbW,GAAG,yBACH,GAAG,GACD,WAAW,CA+EvB;IAED;;;;;;;;;;;OAWG;IACH,6BAHW,UAAU,GAAG,SAAS,GACpB,WAAW,CA8IvB;IAED;;;;;;;;OAQG;IACH,uBAHW,MAAM,EAAE,GACN,WAAW,CA2BvB;IAED;;;;;;;;;;;;OAYG;IACH,2BALW,MAAM,+BAEN,MAAM,GACJ,WAAW,CA2FvB;IAED;;;;;;;;;OASG;IACH,2CAiCC;IAED;;;;;;;;;;OAUG;IACH,4CAOC;CACD;oCA50ByF,cAAc;+BAAd,cAAc;4BAAd,cAAc;kCAAd,cAAc;gCAAd,cAAc;8BAAd,cAAc"}
|
|
@@ -18,8 +18,10 @@ import {
|
|
|
18
18
|
apply_op,
|
|
19
19
|
count_references_excluding_deleted,
|
|
20
20
|
validate_node,
|
|
21
|
+
fill_node_defaults,
|
|
21
22
|
get_active_annotation,
|
|
22
|
-
validate_selection
|
|
23
|
+
validate_selection,
|
|
24
|
+
can_switch_annotation_type
|
|
23
25
|
} from './doc_utils.js';
|
|
24
26
|
|
|
25
27
|
/**
|
|
@@ -60,6 +62,13 @@ export default class Transaction {
|
|
|
60
62
|
this.inverse_ops = [];
|
|
61
63
|
// Remember the selection before the transaction started
|
|
62
64
|
this.selection_before = selection;
|
|
65
|
+
|
|
66
|
+
/** @type {NodeId[]} */
|
|
67
|
+
this.created_node_ids = [];
|
|
68
|
+
/** @type {NodeId[]} */
|
|
69
|
+
this.modified_node_ids = [];
|
|
70
|
+
/** @type {NodeId[]} */
|
|
71
|
+
this.deleted_node_ids = [];
|
|
63
72
|
}
|
|
64
73
|
|
|
65
74
|
/**
|
|
@@ -119,9 +128,10 @@ export default class Transaction {
|
|
|
119
128
|
* @throws {Error} Throws if the node is invalid
|
|
120
129
|
*/
|
|
121
130
|
validate_node(node) {
|
|
122
|
-
validate_node(node, this.schema, this.doc.nodes);
|
|
131
|
+
validate_node(node, this.schema, this.doc.nodes, { require_references: false });
|
|
123
132
|
}
|
|
124
133
|
|
|
134
|
+
|
|
125
135
|
/**
|
|
126
136
|
* Gets all nodes referenced by a given node (recursively).
|
|
127
137
|
*
|
|
@@ -166,6 +176,15 @@ export default class Transaction {
|
|
|
166
176
|
this.doc = apply_op(this.doc, op);
|
|
167
177
|
}
|
|
168
178
|
|
|
179
|
+
/**
|
|
180
|
+
* @param {NodeId[]} node_ids
|
|
181
|
+
* @param {NodeId} node_id
|
|
182
|
+
* @private
|
|
183
|
+
*/
|
|
184
|
+
_track_node_id(node_ids, node_id) {
|
|
185
|
+
if (!node_ids.includes(node_id)) node_ids.push(node_id);
|
|
186
|
+
}
|
|
187
|
+
|
|
169
188
|
/**
|
|
170
189
|
* Sets a property of a node to a new value.
|
|
171
190
|
*
|
|
@@ -183,6 +202,13 @@ export default class Transaction {
|
|
|
183
202
|
* ```
|
|
184
203
|
*/
|
|
185
204
|
set(path, value) {
|
|
205
|
+
const path_info = this.inspect(path);
|
|
206
|
+
if (path_info?.kind !== 'property') {
|
|
207
|
+
throw new Error(
|
|
208
|
+
`Transaction.set requires a path that points to a property, got ${JSON.stringify(path)}`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
186
212
|
const node = this.get(path.slice(0, -1));
|
|
187
213
|
|
|
188
214
|
// Turns ["page_1", "body", "0", "description"]
|
|
@@ -192,9 +218,6 @@ export default class Transaction {
|
|
|
192
218
|
|
|
193
219
|
// Just to be sure, make a deep copy of the old value
|
|
194
220
|
const property_key = path.at(-1);
|
|
195
|
-
if (property_key === undefined) {
|
|
196
|
-
throw new Error('Invalid path: cannot get property key');
|
|
197
|
-
}
|
|
198
221
|
const property_key_str = String(property_key);
|
|
199
222
|
const previous_value = structuredClone($state.snapshot(node[property_key_str]));
|
|
200
223
|
|
|
@@ -218,10 +241,17 @@ export default class Transaction {
|
|
|
218
241
|
this.ops.push(op);
|
|
219
242
|
this.inverse_ops.push(['set', normalized_path, previous_value]);
|
|
220
243
|
this._apply_op(op);
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
244
|
+
this._track_node_id(this.modified_node_ids, node.id);
|
|
245
|
+
|
|
246
|
+
// Garbage-collect nodes whose only reference was via this property.
|
|
247
|
+
// We must use a ref-count-aware sweep instead of unconditional deletion
|
|
248
|
+
// because a node ID removed from this array may still be referenced
|
|
249
|
+
// elsewhere — either from a pre-existing reference (e.g. shared nodes)
|
|
250
|
+
// or from a node created earlier in the same transaction (the
|
|
251
|
+
// wrap-in-group pattern: create a wrapper that points at the children,
|
|
252
|
+
// then set the parent array to drop them).
|
|
253
|
+
if (removed_node_ids.length > 0) {
|
|
254
|
+
this._cascade_delete_unreferenced_nodes(removed_node_ids);
|
|
225
255
|
}
|
|
226
256
|
|
|
227
257
|
return this;
|
|
@@ -238,48 +268,28 @@ export default class Transaction {
|
|
|
238
268
|
for (const node of depth_first_nodes) {
|
|
239
269
|
const new_id = this.generate_id();
|
|
240
270
|
id_map[node.id] = new_id;
|
|
241
|
-
|
|
271
|
+
let new_node = { ...node, id: new_id };
|
|
242
272
|
const node_schema = this.schema[node.type];
|
|
243
273
|
|
|
244
274
|
// Update all property references to use new IDs
|
|
245
275
|
for (const [property_name, property_definition] of Object.entries(node_schema.properties)) {
|
|
246
276
|
const prop_type = property_definition.type;
|
|
247
|
-
const value =
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
new_node[property_name] =
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (value) {
|
|
260
|
-
const annotations = value.annotations.map((annotation) => {
|
|
261
|
-
const { start_offset, end_offset, node_id } = annotation;
|
|
262
|
-
return { start_offset, end_offset, node_id: id_map[node_id] || node_id };
|
|
263
|
-
});
|
|
264
|
-
new_node[property_name] = { text: value.text, annotations };
|
|
265
|
-
} else {
|
|
266
|
-
new_node[property_name] = { text: '', annotations: [] };
|
|
267
|
-
}
|
|
268
|
-
} else if (prop_type === 'string') {
|
|
269
|
-
new_node[property_name] = value ?? property_definition.default ?? '';
|
|
270
|
-
} else if (prop_type === 'integer') {
|
|
271
|
-
new_node[property_name] = value ?? property_definition.default ?? 0;
|
|
272
|
-
} else if (prop_type === 'number') {
|
|
273
|
-
new_node[property_name] = value ?? property_definition.default ?? 0;
|
|
274
|
-
} else if (prop_type === 'boolean') {
|
|
275
|
-
new_node[property_name] = value ?? property_definition.default ?? false;
|
|
276
|
-
} else if (['integer_array', 'number_array'].includes(prop_type)) {
|
|
277
|
-
new_node[property_name] = value ?? property_definition.default ?? [];
|
|
278
|
-
} else if (prop_type === 'string_array') {
|
|
279
|
-
new_node[property_name] = value ?? property_definition.default ?? [];
|
|
277
|
+
const value = new_node[property_name];
|
|
278
|
+
|
|
279
|
+
if (prop_type === 'node_array' && Array.isArray(value)) {
|
|
280
|
+
new_node[property_name] = value.map((ref_id) => id_map[ref_id]);
|
|
281
|
+
} else if (prop_type === 'node' && typeof value === 'string') {
|
|
282
|
+
new_node[property_name] = id_map[value];
|
|
283
|
+
} else if (prop_type === 'annotated_text' && value) {
|
|
284
|
+
const annotations = value.annotations.map((annotation) => {
|
|
285
|
+
const { start_offset, end_offset, node_id } = annotation;
|
|
286
|
+
return { start_offset, end_offset, node_id: id_map[node_id] || node_id };
|
|
287
|
+
});
|
|
288
|
+
new_node[property_name] = { text: value.text, annotations };
|
|
280
289
|
}
|
|
281
290
|
}
|
|
282
291
|
|
|
292
|
+
new_node = fill_node_defaults(new_node, this.schema);
|
|
283
293
|
this.create(new_node);
|
|
284
294
|
}
|
|
285
295
|
|
|
@@ -290,9 +300,9 @@ export default class Transaction {
|
|
|
290
300
|
* Creates a new node in the document.
|
|
291
301
|
*
|
|
292
302
|
* The node must have a valid id and must not already exist in the document.
|
|
293
|
-
*
|
|
303
|
+
* Omitted properties with schema defaults are filled before validation.
|
|
294
304
|
*
|
|
295
|
-
* @param {any} node - The node object to create (must include id, type, and
|
|
305
|
+
* @param {any} node - The node object to create (must include id, type, and required properties)
|
|
296
306
|
* @returns {Transaction} This transaction instance for method chaining
|
|
297
307
|
* @throws {Error} If the node ID is invalid or if the node already exists
|
|
298
308
|
*
|
|
@@ -306,17 +316,20 @@ export default class Transaction {
|
|
|
306
316
|
* ```
|
|
307
317
|
*/
|
|
308
318
|
create(node) {
|
|
319
|
+
const node_with_defaults = fill_node_defaults(node, this.schema);
|
|
320
|
+
|
|
309
321
|
// Validate node against schema
|
|
310
|
-
this.validate_node(
|
|
322
|
+
this.validate_node(node_with_defaults);
|
|
311
323
|
|
|
312
|
-
if (this.get(
|
|
313
|
-
throw new Error('Node with id ' +
|
|
324
|
+
if (this.get(node_with_defaults.id)) {
|
|
325
|
+
throw new Error('Node with id ' + node_with_defaults.id + ' already exists');
|
|
314
326
|
}
|
|
315
327
|
|
|
316
|
-
const op = ['create',
|
|
328
|
+
const op = ['create', node_with_defaults];
|
|
317
329
|
this.ops.push(op);
|
|
318
|
-
this.inverse_ops.push(['delete',
|
|
330
|
+
this.inverse_ops.push(['delete', node_with_defaults.id]);
|
|
319
331
|
this._apply_op(op);
|
|
332
|
+
this._track_node_id(this.created_node_ids, node_with_defaults.id);
|
|
320
333
|
return this;
|
|
321
334
|
}
|
|
322
335
|
|
|
@@ -325,6 +338,13 @@ export default class Transaction {
|
|
|
325
338
|
*
|
|
326
339
|
* The node's current state is captured for undo support before deletion.
|
|
327
340
|
*
|
|
341
|
+
* NOTE: This is a force-delete and intentionally does NOT check whether
|
|
342
|
+
* the node is still referenced from elsewhere — callers (e.g.
|
|
343
|
+
* `annotate_text`) sometimes need to remove a node while a stale
|
|
344
|
+
* reference to it still exists in the property they are about to update.
|
|
345
|
+
* For ref-count-aware cleanup, see how `set` calls
|
|
346
|
+
* `_cascade_delete_unreferenced_nodes`.
|
|
347
|
+
*
|
|
328
348
|
* @param {any} id - The ID of the node to delete
|
|
329
349
|
* @returns {Transaction} This transaction instance for method chaining
|
|
330
350
|
*
|
|
@@ -345,6 +365,7 @@ export default class Transaction {
|
|
|
345
365
|
this.ops.push(op);
|
|
346
366
|
this.inverse_ops.push(['create', previous_value]);
|
|
347
367
|
this._apply_op(op);
|
|
368
|
+
this._track_node_id(this.deleted_node_ids, id);
|
|
348
369
|
// Cascade delete any nodes that are now orphaned after removing this node
|
|
349
370
|
this._cascade_delete_unreferenced_nodes(referenced_nodes);
|
|
350
371
|
return this;
|
|
@@ -402,6 +423,8 @@ export default class Transaction {
|
|
|
402
423
|
const annotations = annotated_text.annotations;
|
|
403
424
|
const existing_annotation = this.active_annotation();
|
|
404
425
|
const existing_annotation_same_type = this.active_annotation(annotation_type);
|
|
426
|
+
const has_annotation_properties =
|
|
427
|
+
annotation_properties && Object.keys(annotation_properties).length > 0;
|
|
405
428
|
|
|
406
429
|
if (existing_annotation) {
|
|
407
430
|
// If there's an existing annotation of the same type, remove it
|
|
@@ -417,16 +440,30 @@ export default class Transaction {
|
|
|
417
440
|
annotations.splice(index, 1);
|
|
418
441
|
}
|
|
419
442
|
} else {
|
|
420
|
-
|
|
443
|
+
const existing_annotation_node = this.get(existing_annotation.node_id);
|
|
444
|
+
const can_switch_annotation =
|
|
445
|
+
!has_annotation_properties &&
|
|
446
|
+
this.available_annotation_types.includes(annotation_type) &&
|
|
447
|
+
can_switch_annotation_type(
|
|
448
|
+
this.schema,
|
|
449
|
+
existing_annotation_node,
|
|
450
|
+
annotation_type
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
if (can_switch_annotation) {
|
|
454
|
+
this.set([existing_annotation.node_id, 'type'], annotation_type);
|
|
455
|
+
return this;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// If there's an annotation of a different incompatible type, don't add a new one
|
|
421
459
|
return this;
|
|
422
460
|
}
|
|
423
461
|
} else {
|
|
424
462
|
if (is_selection_collapsed(this.selection)) {
|
|
425
|
-
console.log('Annotations can only be added to expanded text selections.');
|
|
426
463
|
return this;
|
|
427
464
|
}
|
|
428
465
|
if (!this.available_annotation_types.includes(annotation_type)) {
|
|
429
|
-
console.
|
|
466
|
+
console.warn(`Annotation type ${annotation_type} is not allowed here.`);
|
|
430
467
|
return this;
|
|
431
468
|
}
|
|
432
469
|
const new_annotation_node = {
|
|
@@ -449,19 +486,25 @@ export default class Transaction {
|
|
|
449
486
|
}
|
|
450
487
|
|
|
451
488
|
/**
|
|
452
|
-
* Deletes the currently selected text or
|
|
489
|
+
* Deletes the currently selected text, node, or property.
|
|
453
490
|
*
|
|
454
491
|
* Behavior depends on selection type:
|
|
455
492
|
* - For node selections: Removes selected nodes and cascades deletion of unreferenced nodes
|
|
456
493
|
* - For text selections: Removes selected text and adjusts annotations accordingly
|
|
457
494
|
* - For collapsed selections: Deletes the previous character/node (backward) or next character/node (forward)
|
|
458
|
-
* -
|
|
495
|
+
* - For property selections: Delegates to the config's handle_property_deletion hook
|
|
459
496
|
*
|
|
460
497
|
* @param {'backward' | 'forward'} [direction] - Direction of deletion for collapsed selections
|
|
461
498
|
* @returns {Transaction} This transaction instance for method chaining
|
|
462
499
|
*/
|
|
463
500
|
delete_selection(direction = 'backward') {
|
|
464
|
-
if (!this.selection
|
|
501
|
+
if (!this.selection) return this;
|
|
502
|
+
|
|
503
|
+
if (this.selection.type === 'property') {
|
|
504
|
+
this.config.handle_property_deletion?.(this, this.selection.path);
|
|
505
|
+
return this;
|
|
506
|
+
}
|
|
507
|
+
|
|
465
508
|
const path = this.selection.path;
|
|
466
509
|
|
|
467
510
|
// Get the start and end indices for the selection
|
|
@@ -579,13 +622,11 @@ export default class Transaction {
|
|
|
579
622
|
|
|
580
623
|
text.annotations = new_annotations;
|
|
581
624
|
|
|
582
|
-
// Delete marked annotation nodes
|
|
583
|
-
for (const node_id of _deleted_nodes) {
|
|
584
|
-
this.delete(node_id);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
625
|
this.set(path, text);
|
|
588
626
|
|
|
627
|
+
// Delete marked annotation nodes only if no other references remain.
|
|
628
|
+
this._cascade_delete_unreferenced_nodes(_deleted_nodes);
|
|
629
|
+
|
|
589
630
|
// Update the selection to the new caret position
|
|
590
631
|
this.selection = {
|
|
591
632
|
type: 'text',
|
|
@@ -778,6 +819,7 @@ export default class Transaction {
|
|
|
778
819
|
this.ops.push(op);
|
|
779
820
|
this.inverse_ops.push(['create', previous_value]);
|
|
780
821
|
this._apply_op(op);
|
|
822
|
+
this._track_node_id(this.deleted_node_ids, node_id);
|
|
781
823
|
}
|
|
782
824
|
}
|
|
783
825
|
}
|
package/dist/doc_utils.d.ts
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Similar to your define_schema pattern but for document schemas.
|
|
4
4
|
*
|
|
5
5
|
* @template {Record<string, NodeSchema>} S
|
|
6
|
-
* @param {S} schema - The document schema to validate
|
|
6
|
+
* @param {S & ValidateDocumentSchema<S>} schema - The document schema to validate
|
|
7
7
|
* @returns {S} The same schema, but with type information preserved
|
|
8
8
|
*/
|
|
9
|
-
export function define_document_schema<S extends Record<string, NodeSchema>>(schema: S): S;
|
|
9
|
+
export function define_document_schema<S extends Record<string, NodeSchema>>(schema: S & ValidateDocumentSchema<S>): S;
|
|
10
10
|
/**
|
|
11
11
|
* Check if a string represents a valid primitive type.
|
|
12
12
|
* @param {string} type - The type string to check
|
|
@@ -19,20 +19,61 @@ export function is_primitive_type(type: string): type is PrimitiveType;
|
|
|
19
19
|
* @returns {string | null} The default node type, or null if none specified
|
|
20
20
|
*/
|
|
21
21
|
export function get_default_node_type(property_definition: NodeProperty | NodeArrayProperty): string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Fill omitted properties with schema defaults and Svedit's built-in type defaults.
|
|
24
|
+
*
|
|
25
|
+
* This is a convenience helper for schema evolution, not a complete document
|
|
26
|
+
* migration system. Callers are still responsible for proper migrations when
|
|
27
|
+
* schema changes cannot be represented by defaults, such as property renames or
|
|
28
|
+
* data transformations.
|
|
29
|
+
*
|
|
30
|
+
* @param {any} node - The node to fill defaults for
|
|
31
|
+
* @param {DocumentSchema} schema - The document schema
|
|
32
|
+
* @returns {any} A shallow copy of the node with cloned default values filled in
|
|
33
|
+
*/
|
|
34
|
+
export function fill_node_defaults(node: any, schema: DocumentSchema): any;
|
|
35
|
+
/**
|
|
36
|
+
* Fill omitted properties with schema defaults and Svedit's built-in type defaults across a document.
|
|
37
|
+
*
|
|
38
|
+
* This does not infer values, rename fields, or make an invalid migration valid
|
|
39
|
+
* by itself. Call this as one step in an explicit document migration when it is
|
|
40
|
+
* appropriate.
|
|
41
|
+
*
|
|
42
|
+
* @param {Document} doc - The document to fill defaults for
|
|
43
|
+
* @param {DocumentSchema} schema - The document schema
|
|
44
|
+
* @returns {Document} A document copy with cloned default values filled in
|
|
45
|
+
*/
|
|
46
|
+
export function fill_document_defaults(doc: Document, schema: DocumentSchema): Document;
|
|
22
47
|
/**
|
|
23
48
|
* Validate a document schema to ensure all referenced node types exist.
|
|
24
49
|
* @param {DocumentSchema} document_schema - The document schema to validate
|
|
25
50
|
* @throws {Error} Throws if the document schema is invalid
|
|
26
51
|
*/
|
|
27
52
|
export function validate_document_schema(document_schema: DocumentSchema): void;
|
|
53
|
+
/**
|
|
54
|
+
* @param {string} id
|
|
55
|
+
* @returns {boolean}
|
|
56
|
+
*/
|
|
57
|
+
export function is_id_valid(id: string): boolean;
|
|
28
58
|
/**
|
|
29
59
|
* Validate a node against its schema.
|
|
30
60
|
* @param {any} node - The node to validate
|
|
31
61
|
* @param {DocumentSchema} schema - The document schema
|
|
32
62
|
* @param {Record<string, any>} all_nodes - All nodes in the document to check references
|
|
63
|
+
* @param {{ require_references?: boolean }} [options] - Validation options
|
|
33
64
|
* @throws {Error} Throws if the node is invalid
|
|
34
65
|
*/
|
|
35
|
-
export function validate_node(node: any, schema: DocumentSchema, all_nodes?: Record<string, any
|
|
66
|
+
export function validate_node(node: any, schema: DocumentSchema, all_nodes?: Record<string, any>, options?: {
|
|
67
|
+
require_references?: boolean;
|
|
68
|
+
}): void;
|
|
69
|
+
/**
|
|
70
|
+
* Validate a document against its schema.
|
|
71
|
+
*
|
|
72
|
+
* @param {Document} doc - The document to validate
|
|
73
|
+
* @param {DocumentSchema} schema - The document schema
|
|
74
|
+
* @throws {Error} Throws if the document is invalid
|
|
75
|
+
*/
|
|
76
|
+
export function validate_document(doc: Document, schema: DocumentSchema): void;
|
|
36
77
|
/**
|
|
37
78
|
* Gets a value from the document at the specified path.
|
|
38
79
|
*
|
|
@@ -59,6 +100,16 @@ export function property_type(schema: object, type: string, property: string): s
|
|
|
59
100
|
* @returns {'block'|'text'|'annotation'} The node kind
|
|
60
101
|
*/
|
|
61
102
|
export function kind(schema: object, node: any): "block" | "text" | "annotation";
|
|
103
|
+
/**
|
|
104
|
+
* Checks whether an annotation node can be switched to another annotation type
|
|
105
|
+
* without losing data.
|
|
106
|
+
*
|
|
107
|
+
* @param {DocumentSchema} schema - The document schema
|
|
108
|
+
* @param {any} annotation_node - The annotation node to switch
|
|
109
|
+
* @param {string} target_annotation_type - The target annotation type
|
|
110
|
+
* @returns {boolean} True if the annotation can be switched safely
|
|
111
|
+
*/
|
|
112
|
+
export function can_switch_annotation_type(schema: DocumentSchema, annotation_node: any, target_annotation_type: string): boolean;
|
|
62
113
|
/**
|
|
63
114
|
* Inspects a path to get metadata about the value at that location.
|
|
64
115
|
*
|
|
@@ -114,6 +165,15 @@ export function get_active_annotation(schema: DocumentSchema, doc: Document, sel
|
|
|
114
165
|
* @returns {number} The number of references excluding deleted nodes
|
|
115
166
|
*/
|
|
116
167
|
export function count_references_excluding_deleted(schema: DocumentSchema, doc: Document, target_node_id: NodeId, nodes_to_delete: Record<NodeId, boolean>): number;
|
|
168
|
+
/**
|
|
169
|
+
* Gets ids of nodes that reference any of the target nodes.
|
|
170
|
+
*
|
|
171
|
+
* @param {DocumentSchema} schema - The document schema
|
|
172
|
+
* @param {Document} doc - The document containing nodes
|
|
173
|
+
* @param {Iterable<NodeId>} target_node_ids - Node IDs to find referrers for
|
|
174
|
+
* @returns {NodeId[]} IDs of nodes with references to any target node
|
|
175
|
+
*/
|
|
176
|
+
export function get_referencing_node_ids(schema: DocumentSchema, doc: Document, target_node_ids: Iterable<NodeId>): NodeId[];
|
|
117
177
|
/**
|
|
118
178
|
* Validates a selection against the current document state.
|
|
119
179
|
* Works with any object that implements get() and inspect() (Session or Transaction).
|
|
@@ -127,6 +187,7 @@ export function validate_selection(selection: Selection, session_or_transaction:
|
|
|
127
187
|
inspect: Function;
|
|
128
188
|
}): void;
|
|
129
189
|
import type { NodeSchema } from './types';
|
|
190
|
+
import type { ValidateDocumentSchema } from './types';
|
|
130
191
|
import type { PrimitiveType } from './types';
|
|
131
192
|
import type { NodeProperty } from './types';
|
|
132
193
|
import type { NodeArrayProperty } from './types';
|
package/dist/doc_utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doc_utils.d.ts","sourceRoot":"","sources":["../src/lib/doc_utils.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"doc_utils.d.ts","sourceRoot":"","sources":["../src/lib/doc_utils.js"],"names":[],"mappings":"AAiBA;;;;;;;GAOG;AACH,uCAJ0C,CAAC,SAA7B,MAAM,CAAC,MAAM,EAAE,UAAU,CAAE,UAC9B,CAAC,GAAG,uBAAuB,CAAC,CAAC,GAC3B,CAAC,CAIb;AAED;;;;GAIG;AACH,wCAHW,MAAM,GACJ,IAAI,IAAI,aAAa,CAejC;AAED;;;;GAIG;AACH,2DAHW,YAAY,GAAG,iBAAiB,GAC9B,MAAM,GAAG,IAAI,CAWzB;AA2BD;;;;;;;;;;;GAWG;AACH,yCAJW,GAAG,UACH,cAAc,GACZ,GAAG,CAgBf;AAED;;;;;;;;;;GAUG;AACH,4CAJW,QAAQ,UACR,cAAc,GACZ,QAAQ,CAcpB;AAED;;;;GAIG;AACH,0DAHW,cAAc,QAoBxB;AAoHD;;;GAGG;AACH,gCAHW,MAAM,GACJ,OAAO,CAInB;AAED;;;;;;;GAOG;AACH,oCANW,GAAG,UACH,cAAc,cACd,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,YACnB;IAAE,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAAE,QAwF1C;AAED;;;;;;GAMG;AACH,uCAJW,QAAQ,UACR,cAAc,QAUxB;AAED;;;;;;;GAOG;AACH,4BALW,cAAc,OACd,QAAQ,QACR,YAAY,GAAC,MAAM,GACjB,GAAG,CA4Ef;AAED;;;;;;;GAOG;AACH,sCALW,MAAM,QACN,MAAM,YACN,MAAM,GACJ,MAAM,CAclB;AAED;;;;;;GAMG;AACH,6BAJW,MAAM,QACN,GAAG,GACD,OAAO,GAAC,MAAM,GAAC,YAAY,CAIvC;AAED;;;;;;;;GAQG;AACH,mDALW,cAAc,mBACd,GAAG,0BACH,MAAM,GACJ,OAAO,CAgBnB;AAED;;;;;;;GAOG;AACH,gCALW,cAAc,OACd,QAAQ,QACR,YAAY,GACV;IAAC,IAAI,EAAE,UAAU,GAAC,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAC,CAoBzD;AAED;;;;;;;GAOG;AACH,8BAJW,QAAQ,cAEN,QAAQ,CAkCpB;AAED;;;;;;;GAOG;AACH,yCALW,cAAc,OACd,QAAQ,WACR,MAAM,GACJ,MAAM,CAoBlB;AAED;;;;;;;;;;;;;GAaG;AACH,8CANW,cAAc,OACd,QAAQ,cACR,SAAS,oBACT,MAAM,GACJ,UAAU,GAAG,IAAI,CAwB7B;AAED;;;;;;;;GAQG;AACH,2DANW,cAAc,OACd,QAAQ,kBACR,MAAM,mBACN,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACrB,MAAM,CA0BlB;AAED;;;;;;;GAOG;AACH,iDALW,cAAc,OACd,QAAQ,mBACR,QAAQ,CAAC,MAAM,CAAC,GACd,MAAM,EAAE,CA4BpB;AAED;;;;;;;GAOG;AACH,8CAJW,SAAS,0BACT;IAAE,GAAG,WAAW;IAAC,OAAO,WAAU;CAAE,QAoD9C;gCAhyBmN,SAAS;4CAAT,SAAS;mCAAT,SAAS;kCAAT,SAAS;uCAAT,SAAS;oCAAT,SAAS;8BAAT,SAAS;kCAAT,SAAS;4BAAT,SAAS;+BAAT,SAAS;gCAAT,SAAS"}
|