svedit 0.2.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 +41 -21
- package/dist/AnnotatedTextProperty.svelte +117 -0
- package/dist/AnnotatedTextProperty.svelte.d.ts +7 -0
- package/dist/AnnotatedTextProperty.svelte.d.ts.map +1 -0
- package/dist/CustomProperty.svelte +20 -12
- package/dist/CustomProperty.svelte.d.ts +1 -2
- package/dist/CustomProperty.svelte.d.ts.map +1 -1
- package/dist/Document.svelte.d.ts +40 -17
- package/dist/Document.svelte.d.ts.map +1 -1
- package/dist/Document.svelte.js +192 -77
- package/dist/Node.svelte +18 -14
- package/dist/Node.svelte.d.ts.map +1 -1
- package/dist/NodeArrayProperty.svelte +1 -1
- package/dist/Svedit.svelte +628 -109
- package/dist/Svedit.svelte.d.ts +1 -1
- package/dist/Svedit.svelte.d.ts.map +1 -1
- package/dist/Transaction.svelte.d.ts +4 -7
- package/dist/Transaction.svelte.d.ts.map +1 -1
- package/dist/Transaction.svelte.js +74 -81
- package/dist/commands.svelte.d.ts.map +1 -1
- package/dist/commands.svelte.js +20 -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 +125 -3
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +191 -34
- package/package.json +1 -1
- package/src/lib/AnnotatedTextProperty.svelte +117 -0
- package/src/lib/CustomProperty.svelte +20 -12
- package/src/lib/Document.svelte.js +192 -77
- package/src/lib/Node.svelte +18 -14
- package/src/lib/NodeArrayProperty.svelte +1 -1
- package/src/lib/Svedit.svelte +628 -109
- package/src/lib/Transaction.svelte.js +74 -81
- package/src/lib/commands.svelte.js +20 -10
- package/src/lib/index.js +1 -1
- package/src/lib/types.d.ts +66 -21
- package/src/lib/util.js +191 -34
- package/dist/AnnotatedStringProperty.svelte +0 -126
- package/dist/AnnotatedStringProperty.svelte.d.ts +0 -7
- package/dist/AnnotatedStringProperty.svelte.d.ts.map +0 -1
- package/src/lib/AnnotatedStringProperty.svelte +0 -126
package/dist/Svedit.svelte.d.ts
CHANGED
|
@@ -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":";;;;;;;AAo6CA;;eAAqD;iCAx5CgE,cAAc"}
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @import { NodeId, SerializedNode, Selection, DocumentPath } from './types.js'
|
|
3
|
-
*/
|
|
4
1
|
/**
|
|
5
2
|
* Transaction class for managing atomic document operations with undo/redo support.
|
|
6
3
|
*
|
|
@@ -54,7 +51,7 @@ export default class Transaction {
|
|
|
54
51
|
* @example
|
|
55
52
|
* ```js
|
|
56
53
|
* tr.set(["list_1", "list_items"], [1, 2, 3]);
|
|
57
|
-
* tr.set(["page_1", "body", "0", "description"],
|
|
54
|
+
* tr.set(["page_1", "body", "0", "description"], {text: "Hello world", annotations: []});
|
|
58
55
|
* ```
|
|
59
56
|
*/
|
|
60
57
|
set(path: DocumentPath, value: any): void;
|
|
@@ -97,7 +94,7 @@ export default class Transaction {
|
|
|
97
94
|
*
|
|
98
95
|
* @param {Selection} selection - The new selection object
|
|
99
96
|
* @returns {Transaction} This transaction instance for method chaining
|
|
100
|
-
* @
|
|
97
|
+
* @throws {Error} Throws if the selection is invalid or out of bounds
|
|
101
98
|
*/
|
|
102
99
|
set_selection(selection: Selection): Transaction;
|
|
103
100
|
/**
|
|
@@ -108,7 +105,7 @@ export default class Transaction {
|
|
|
108
105
|
* when conflicting types are applied.
|
|
109
106
|
*
|
|
110
107
|
* @param {any} annotation_type - The type of annotation (e.g., 'link', 'bold', 'italic')
|
|
111
|
-
* @param {any}
|
|
108
|
+
* @param {any} annotation_properties - Additional data for the annotation (e.g., href for links)
|
|
112
109
|
* @returns {Transaction} This transaction instance for method chaining
|
|
113
110
|
*
|
|
114
111
|
* @example
|
|
@@ -120,7 +117,7 @@ export default class Transaction {
|
|
|
120
117
|
* tr.annotate_text('emphasis', {});
|
|
121
118
|
* ```
|
|
122
119
|
*/
|
|
123
|
-
annotate_text(annotation_type: any,
|
|
120
|
+
annotate_text(annotation_type: any, annotation_properties: any): Transaction;
|
|
124
121
|
/**
|
|
125
122
|
* Deletes the currently selected text or nodes.
|
|
126
123
|
*
|
|
@@ -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":"AAMA;;;;;;;;;;;;;;GAcG;AACH;IACE;;;;OAIG;IACH,iBAFW,GAAG,EAWb;IARC,SAAc;IAEd,oCAAoC;IACpC,KADW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CACnB;IACb,oCAAoC;IACpC,aADW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CACX;IAErB,sBAA0C;IAG5C;;;;;OAKG;IACH,UAHW,YAAY,GACV,GAAG,CAIf;IAED;;;;OAIG;IACH,iBAFa,SAAS,CAIrB;IAED;;;;;;;;;;;;;;OAcG;IACH,UATW,YAAY,SACZ,GAAG,QA2Bb;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACH,aAbW,GAAG,GACD,WAAW,CAyBvB;IAED;;;;;;;;;;;;OAYG;IACH,WARW,GAAG,GACD,WAAW,CAcvB;IAED;;;;;;OAMG;IACH,yBAJW,SAAS,GACP,WAAW,CAOvB;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,+BAbW,GAAG,yBACH,GAAG,GACD,WAAW,CAuDvB;IAED;;;;;;;;;OASG;IACH,oBAFa,WAAW,CAyGvB;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACH,oBAbW,cAAc,EAAE,GACd,WAAW,CAqDvB;IAED;;;;;;;;;;;;;;;OAeG;IACH,2BAVW,MAAM,GACJ,WAAW,CA8EvB;IAED;;;;;;;;OAQG;IACH,2CA+BC;IAED;;;;;;;;;;OAUG;IACH,4CAuBC;CACF;kCAxjBmE,YAAY;+BAAZ,YAAY;oCAAZ,YAAY"}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* @import { NodeId, SerializedNode, Selection, DocumentPath } from './types.js'
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { get_char_length, char_slice } from './util.js';
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* Transaction class for managing atomic document operations with undo/redo support.
|
|
7
9
|
*
|
|
@@ -65,7 +67,7 @@ export default class Transaction {
|
|
|
65
67
|
* @example
|
|
66
68
|
* ```js
|
|
67
69
|
* tr.set(["list_1", "list_items"], [1, 2, 3]);
|
|
68
|
-
* tr.set(["page_1", "body", "0", "description"],
|
|
70
|
+
* tr.set(["page_1", "body", "0", "description"], {text: "Hello world", annotations: []});
|
|
69
71
|
* ```
|
|
70
72
|
*/
|
|
71
73
|
set (path, value) {
|
|
@@ -150,10 +152,10 @@ export default class Transaction {
|
|
|
150
152
|
*
|
|
151
153
|
* @param {Selection} selection - The new selection object
|
|
152
154
|
* @returns {Transaction} This transaction instance for method chaining
|
|
153
|
-
* @
|
|
155
|
+
* @throws {Error} Throws if the selection is invalid or out of bounds
|
|
154
156
|
*/
|
|
155
157
|
set_selection(selection) {
|
|
156
|
-
|
|
158
|
+
this.doc._validate_selection(selection);
|
|
157
159
|
this.doc.selection = selection;
|
|
158
160
|
return this;
|
|
159
161
|
}
|
|
@@ -166,7 +168,7 @@ export default class Transaction {
|
|
|
166
168
|
* when conflicting types are applied.
|
|
167
169
|
*
|
|
168
170
|
* @param {any} annotation_type - The type of annotation (e.g., 'link', 'bold', 'italic')
|
|
169
|
-
* @param {any}
|
|
171
|
+
* @param {any} annotation_properties - Additional data for the annotation (e.g., href for links)
|
|
170
172
|
* @returns {Transaction} This transaction instance for method chaining
|
|
171
173
|
*
|
|
172
174
|
* @example
|
|
@@ -178,64 +180,25 @@ export default class Transaction {
|
|
|
178
180
|
* tr.annotate_text('emphasis', {});
|
|
179
181
|
* ```
|
|
180
182
|
*/
|
|
181
|
-
annotate_text(annotation_type,
|
|
183
|
+
annotate_text(annotation_type, annotation_properties) {
|
|
182
184
|
if (this.doc.selection.type !== 'text') return this;
|
|
183
185
|
|
|
184
186
|
const { start, end } = this.doc.get_selection_range();
|
|
185
|
-
const
|
|
186
|
-
const annotations =
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
// Special annotation type handling should probably be done in a separate function.
|
|
190
|
-
// The goal is to keep the core logic simple and allow developer to extend and pick only what they need.
|
|
191
|
-
// It could also be abstracted to not check for type (e.g. "link") but for a special attribute
|
|
192
|
-
// e.g. "zero-range-updatable" for annotations that are updatable without a range selection change.
|
|
193
|
-
|
|
194
|
-
// Special handling for links when there's no selection range
|
|
195
|
-
// Links should be updatable by just clicking on them without a range selection
|
|
196
|
-
if (annotation_type === 'link' && start === end && existing_annotations) {
|
|
197
|
-
|
|
198
|
-
// Use findIndex for deep comparison of annotation properties (comparison of annotation properties rather than object reference via indexOf)
|
|
199
|
-
const index = annotations.findIndex(/** @param {any} anno */ (anno) =>
|
|
200
|
-
anno[0] === existing_annotations[0] &&
|
|
201
|
-
anno[1] === existing_annotations[1] &&
|
|
202
|
-
anno[2] === existing_annotations[2]
|
|
203
|
-
);
|
|
204
|
-
// const index = annotations.indexOf(existing_annotations);
|
|
205
|
-
|
|
206
|
-
if (index !== -1) {
|
|
207
|
-
if (annotation_data.href === '') {
|
|
208
|
-
// Remove the annotation if the href is empty
|
|
209
|
-
annotations.splice(index, 1);
|
|
210
|
-
} else {
|
|
211
|
-
annotations[index] = [
|
|
212
|
-
existing_annotations[0],
|
|
213
|
-
existing_annotations[1],
|
|
214
|
-
'link',
|
|
215
|
-
{ ...existing_annotations[3], ...annotation_data }
|
|
216
|
-
];
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
this.set(this.doc.selection.path, annotated_string);
|
|
220
|
-
return this;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Regular annotation handling
|
|
225
|
-
if (start === end) {
|
|
226
|
-
// For non-link annotations: You can not annotate text if the selection is collapsed.
|
|
227
|
-
return this;
|
|
228
|
-
}
|
|
187
|
+
const annotated_text = structuredClone($state.snapshot(this.doc.get(this.doc.selection.path)));
|
|
188
|
+
const annotations = annotated_text.annotations;
|
|
189
|
+
const existing_annotation = this.doc.active_annotation();
|
|
190
|
+
const existing_annotation_same_type = this.doc.active_annotation(annotation_type);
|
|
229
191
|
|
|
230
|
-
if (
|
|
192
|
+
if (existing_annotation) {
|
|
231
193
|
// If there's an existing annotation of the same type, remove it
|
|
232
|
-
if (
|
|
194
|
+
if (existing_annotation_same_type) {
|
|
233
195
|
const index = annotations.findIndex(/** @param {any} anno */ (anno) =>
|
|
234
|
-
anno
|
|
235
|
-
anno
|
|
236
|
-
anno[2] === existing_annotations[2]
|
|
196
|
+
anno.start_offset === existing_annotation.start_offset &&
|
|
197
|
+
anno.end_offset === existing_annotation.end_offset
|
|
237
198
|
);
|
|
238
199
|
if (index !== -1) {
|
|
200
|
+
// Remove the annotation node from the graph
|
|
201
|
+
this.delete(annotations[index].node_id);
|
|
239
202
|
annotations.splice(index, 1);
|
|
240
203
|
}
|
|
241
204
|
} else {
|
|
@@ -243,14 +206,23 @@ export default class Transaction {
|
|
|
243
206
|
return this;
|
|
244
207
|
}
|
|
245
208
|
} else {
|
|
209
|
+
if (!this.doc.available_annotation_types.includes(annotation_type)) {
|
|
210
|
+
console.log(`Annotation type ${annotation_type} is not allowed here.`);
|
|
211
|
+
return this;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const new_annotation_node = {
|
|
215
|
+
id: this.doc.generate_id(),
|
|
216
|
+
type: annotation_type,
|
|
217
|
+
...annotation_properties,
|
|
218
|
+
}
|
|
219
|
+
this.create(new_annotation_node);
|
|
246
220
|
// If there's no existing annotation, add the new one
|
|
247
|
-
annotations.push(
|
|
221
|
+
annotations.push({start_offset: start, end_offset: end, node_id: new_annotation_node.id});
|
|
248
222
|
}
|
|
249
223
|
|
|
250
|
-
// Update the annotated
|
|
251
|
-
this.set(this.doc.selection.path,
|
|
252
|
-
// Not needed anymore as doc.apply makes sure selection is rerendered
|
|
253
|
-
// this.doc.selection = { ...this.doc.selection };
|
|
224
|
+
// Update the annotated text
|
|
225
|
+
this.set(this.doc.selection.path, annotated_text);
|
|
254
226
|
return this;
|
|
255
227
|
}
|
|
256
228
|
|
|
@@ -306,13 +278,16 @@ export default class Transaction {
|
|
|
306
278
|
const path = this.doc.selection.path;
|
|
307
279
|
let text = structuredClone($state.snapshot(this.doc.get(path)));
|
|
308
280
|
|
|
309
|
-
// Update the text content
|
|
310
|
-
|
|
281
|
+
// Update the text content using character-based operations
|
|
282
|
+
const original_text = text.text;
|
|
283
|
+
text.text = char_slice(original_text, 0, start) + char_slice(original_text, end, get_char_length(original_text));
|
|
311
284
|
|
|
285
|
+
// To mark annotation nodes for deletion.
|
|
286
|
+
const _deleted_nodes = [];
|
|
312
287
|
// Update annotation offsets for deletion
|
|
313
288
|
const deletion_length = end - start;
|
|
314
|
-
const new_annotations = text
|
|
315
|
-
const
|
|
289
|
+
const new_annotations = text.annotations.map(/** @param {any} annotation */ (annotation) => {
|
|
290
|
+
const {start_offset: annotation_start, end_offset: annotation_end, node_id} = annotation;
|
|
316
291
|
|
|
317
292
|
// Adjust start offset
|
|
318
293
|
let new_start = annotation_start;
|
|
@@ -338,16 +313,24 @@ export default class Transaction {
|
|
|
338
313
|
|
|
339
314
|
// Remove annotation if it becomes invalid (start >= end)
|
|
340
315
|
if (new_start >= new_end) {
|
|
316
|
+
console.log('deleting annotation');
|
|
317
|
+
_deleted_nodes.push(node_id);
|
|
341
318
|
return false;
|
|
342
319
|
}
|
|
343
320
|
|
|
344
|
-
return
|
|
321
|
+
return {start_offset: new_start, end_offset: new_end, node_id};
|
|
345
322
|
}).filter(Boolean);
|
|
346
323
|
|
|
347
|
-
text
|
|
324
|
+
text.annotations = new_annotations;
|
|
348
325
|
|
|
349
326
|
// Update the text in the entry (this implicitly records an op via this.set)
|
|
350
327
|
this.set(path, text);
|
|
328
|
+
|
|
329
|
+
// Remove annotation nodes that were implicitly deleted.
|
|
330
|
+
for (const node_id of _deleted_nodes) {
|
|
331
|
+
this.delete(node_id);
|
|
332
|
+
}
|
|
333
|
+
|
|
351
334
|
this.set_selection({
|
|
352
335
|
type: 'text',
|
|
353
336
|
path,
|
|
@@ -439,17 +422,18 @@ export default class Transaction {
|
|
|
439
422
|
insert_text(replaced_text) {
|
|
440
423
|
if (this.doc.selection.type !== 'text') return this;
|
|
441
424
|
|
|
442
|
-
const
|
|
425
|
+
const annotated_text = structuredClone($state.snapshot(this.doc.get(this.doc.selection.path)));
|
|
443
426
|
const { start, end } = this.doc.get_selection_range();
|
|
444
427
|
|
|
445
|
-
// Transform the plain text string
|
|
446
|
-
|
|
428
|
+
// Transform the plain text string using character-based operations
|
|
429
|
+
const text = annotated_text.text;
|
|
430
|
+
annotated_text.text = char_slice(text, 0, start) + replaced_text + char_slice(text, end, get_char_length(text));
|
|
447
431
|
|
|
448
|
-
// Transform the annotations (
|
|
449
|
-
// NOTE: Annotations are stored as
|
|
450
|
-
const delta = replaced_text
|
|
451
|
-
const new_annotations =
|
|
452
|
-
const
|
|
432
|
+
// Transform the annotations (annotated_text.annotations)
|
|
433
|
+
// NOTE: Annotations are stored as {start_offset, end_offset, node_id}
|
|
434
|
+
const delta = get_char_length(replaced_text) - (end - start);
|
|
435
|
+
const new_annotations = annotated_text.annotations.map(/** @param {any} annotation */ (annotation) => {
|
|
436
|
+
const {start_offset: annotation_start, end_offset: annotation_end, node_id} = annotation;
|
|
453
437
|
|
|
454
438
|
// Case 1: annotation is wrapped in start and end (remove it)
|
|
455
439
|
if (start <= annotation_start && end >= annotation_end) {
|
|
@@ -458,7 +442,7 @@ export default class Transaction {
|
|
|
458
442
|
|
|
459
443
|
// Case 2: text inserted before the annotation
|
|
460
444
|
if (end <= annotation_start) {
|
|
461
|
-
return
|
|
445
|
+
return {start_offset: annotation_start + delta, end_offset: annotation_end + delta, node_id};
|
|
462
446
|
}
|
|
463
447
|
|
|
464
448
|
// Case 3: text inserted after the annotation
|
|
@@ -469,14 +453,20 @@ export default class Transaction {
|
|
|
469
453
|
// Case 4: annotation is partly selected towards right
|
|
470
454
|
// NOTE: replaced_text will not be part of the annotation, we treat it the same as
|
|
471
455
|
// a cursor right after the annotation
|
|
472
|
-
if (start > annotation_start && start < annotation_end && end
|
|
473
|
-
return
|
|
456
|
+
if (start > annotation_start && start < annotation_end && end >= annotation_end) {
|
|
457
|
+
return {start_offset: annotation_start, end_offset: start, node_id};
|
|
474
458
|
}
|
|
475
459
|
|
|
476
460
|
// Case 5: text inserted inside an annotation
|
|
477
|
-
// NOTE: This also covers the case when a annotation is partly selected towards left
|
|
478
461
|
if (start >= annotation_start && start <= annotation_end && end < annotation_end && end >= annotation_start) {
|
|
479
|
-
return
|
|
462
|
+
return {start_offset: annotation_start, end_offset: annotation_end + delta, node_id};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Case 6: annotation is partly selected towards left
|
|
466
|
+
// NOTE: replaced_text will not be part of the annotation, we treat it the same as
|
|
467
|
+
// a cursor right before the annotation
|
|
468
|
+
if (start < annotation_start && end > annotation_start && end < annotation_end) {
|
|
469
|
+
return {start_offset: start + get_char_length(replaced_text), end_offset: annotation_end + delta, node_id};
|
|
480
470
|
}
|
|
481
471
|
|
|
482
472
|
// Unhandled edge case:
|
|
@@ -486,14 +476,15 @@ export default class Transaction {
|
|
|
486
476
|
// return annotation;
|
|
487
477
|
}).filter(Boolean);
|
|
488
478
|
|
|
489
|
-
|
|
479
|
+
annotated_text.annotations = new_annotations;
|
|
480
|
+
this.set(this.doc.selection.path, annotated_text); // this will update the current state and create a history entry
|
|
490
481
|
|
|
491
482
|
// Setting the selection automatically triggers a re-render of the corresponding DOMSelection.
|
|
492
483
|
const new_selection = {
|
|
493
484
|
type: 'text',
|
|
494
485
|
path: this.doc.selection.path,
|
|
495
|
-
anchor_offset: start + replaced_text
|
|
496
|
-
focus_offset: start + replaced_text
|
|
486
|
+
anchor_offset: start + get_char_length(replaced_text),
|
|
487
|
+
focus_offset: start + get_char_length(replaced_text),
|
|
497
488
|
};
|
|
498
489
|
this.doc.selection = new_selection;
|
|
499
490
|
return this;
|
|
@@ -568,6 +559,8 @@ export default class Transaction {
|
|
|
568
559
|
count += value.filter(id => id === target_node_id).length;
|
|
569
560
|
} else if (prop_type === 'node' && value === target_node_id) {
|
|
570
561
|
count += 1;
|
|
562
|
+
} else if (prop_type === 'annotated_text' && value && value.annotations) {
|
|
563
|
+
count += value.annotations.filter(annotation => annotation.node_id === target_node_id).length;
|
|
571
564
|
}
|
|
572
565
|
}
|
|
573
566
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.svelte.d.ts","sourceRoot":"","sources":["../src/lib/commands.svelte.js"],"names":[],"mappings":"AAGA,
|
|
1
|
+
{"version":3,"file":"commands.svelte.d.ts","sourceRoot":"","sources":["../src/lib/commands.svelte.js"],"names":[],"mappings":"AAGA,kDAqDC;AAED,iDAsEC;AAED,sDA6BC;AAED,6CA6GC"}
|
package/dist/commands.svelte.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { split_annotated_text, join_annotated_text, get_char_length } from './util.js';
|
|
2
2
|
import { get_default_node_type } from './Document.svelte.js';
|
|
3
3
|
|
|
4
4
|
export function break_text_node(tr) {
|
|
@@ -30,7 +30,7 @@ export function break_text_node(tr) {
|
|
|
30
30
|
|
|
31
31
|
const split_at_position = tr.doc.selection.anchor_offset;
|
|
32
32
|
const content = tr.doc.get(selection.path);
|
|
33
|
-
const [left_text, right_text] =
|
|
33
|
+
const [left_text, right_text] = split_annotated_text(content, split_at_position);
|
|
34
34
|
|
|
35
35
|
tr.set([node.id, 'content'], left_text);
|
|
36
36
|
|
|
@@ -42,7 +42,7 @@ export function break_text_node(tr) {
|
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
// TODO: Only use default_node_type when cursor is at the end of
|
|
45
|
-
const node_array_property_definition = doc.schema[node_array_node.type][node_array_prop];
|
|
45
|
+
const node_array_property_definition = doc.schema[node_array_node.type].properties[node_array_prop];
|
|
46
46
|
const target_node_type = get_default_node_type(node_array_property_definition);
|
|
47
47
|
|
|
48
48
|
if (!target_node_type) {
|
|
@@ -53,6 +53,7 @@ export function break_text_node(tr) {
|
|
|
53
53
|
tr.set_selection(node_insert_position);
|
|
54
54
|
|
|
55
55
|
doc.config.inserters[target_node_type](tr, right_text);
|
|
56
|
+
return true;
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
export function join_text_node(tr) {
|
|
@@ -81,7 +82,7 @@ export function join_text_node(tr) {
|
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
// Special behavior: if we can't join and current node is empty, delete it
|
|
84
|
-
if (!can_join && node.content
|
|
85
|
+
if (!can_join && node.content.text === '') {
|
|
85
86
|
tr.set_selection({
|
|
86
87
|
type: 'node',
|
|
87
88
|
path: doc.selection.path.slice(0, -2),
|
|
@@ -99,8 +100,15 @@ export function join_text_node(tr) {
|
|
|
99
100
|
|
|
100
101
|
// Normal joining logic - both nodes are text nodes
|
|
101
102
|
const previous_text_path = [...doc.selection.path.slice(0, -2), node_index - 1];
|
|
102
|
-
const joined_text =
|
|
103
|
+
const joined_text = join_annotated_text(predecessor_node.content, node.content);
|
|
103
104
|
|
|
105
|
+
// Calculate cursor position based on original predecessor content length
|
|
106
|
+
const cursor_position = get_char_length(predecessor_node.content.text);
|
|
107
|
+
|
|
108
|
+
// First set the joined content on the predecessor node (preserves annotations)
|
|
109
|
+
tr.set([predecessor_node.id, 'content'], joined_text);
|
|
110
|
+
|
|
111
|
+
// Then delete the current node
|
|
104
112
|
tr.set_selection({
|
|
105
113
|
type: 'node',
|
|
106
114
|
path: doc.selection.path.slice(0, -2),
|
|
@@ -109,13 +117,15 @@ export function join_text_node(tr) {
|
|
|
109
117
|
});
|
|
110
118
|
|
|
111
119
|
tr.delete_selection();
|
|
120
|
+
|
|
121
|
+
// Finally set the cursor position at the join point using the pre-calculated position
|
|
112
122
|
tr.set_selection({
|
|
113
123
|
type: 'text',
|
|
114
124
|
path: [...previous_text_path, 'content'],
|
|
115
|
-
anchor_offset:
|
|
116
|
-
focus_offset:
|
|
125
|
+
anchor_offset: cursor_position,
|
|
126
|
+
focus_offset: cursor_position,
|
|
117
127
|
});
|
|
118
|
-
|
|
128
|
+
return true;
|
|
119
129
|
}
|
|
120
130
|
|
|
121
131
|
export function insert_default_node(tr) {
|
|
@@ -132,7 +142,7 @@ export function insert_default_node(tr) {
|
|
|
132
142
|
const property_name = path.at(-1);
|
|
133
143
|
|
|
134
144
|
// Get the definition for this property
|
|
135
|
-
const property_definition = doc.schema[node_array_node.type][property_name];
|
|
145
|
+
const property_definition = doc.schema[node_array_node.type].properties[property_name];
|
|
136
146
|
const default_type = get_default_node_type(property_definition);
|
|
137
147
|
|
|
138
148
|
// Only proceed if there's exactly one allowed node_type
|
|
@@ -159,7 +169,7 @@ export function select_all(tr) {
|
|
|
159
169
|
|
|
160
170
|
if (selection.type === 'text') {
|
|
161
171
|
const text_content = doc.get(selection.path);
|
|
162
|
-
const text_length = text_content
|
|
172
|
+
const text_length = get_char_length(text_content.text);
|
|
163
173
|
|
|
164
174
|
// Check if all text is already selected
|
|
165
175
|
const is_all_text_selected =
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { default as Svedit } from "./Svedit.svelte";
|
|
2
|
-
export { default as
|
|
2
|
+
export { default as AnnotatedTextProperty } from "./AnnotatedTextProperty.svelte";
|
|
3
3
|
export { default as CustomProperty } from "./CustomProperty.svelte";
|
|
4
4
|
export { default as Node } from "./Node.svelte";
|
|
5
5
|
export { default as NodeArrayProperty } from "./NodeArrayProperty.svelte";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Main exports for the svedit library
|
|
2
2
|
export { default as Svedit } from './Svedit.svelte';
|
|
3
|
-
export { default as
|
|
3
|
+
export { default as AnnotatedTextProperty } from './AnnotatedTextProperty.svelte';
|
|
4
4
|
export { default as CustomProperty } from './CustomProperty.svelte';
|
|
5
5
|
export { default as Node } from './Node.svelte';
|
|
6
6
|
export { default as NodeArrayProperty } from './NodeArrayProperty.svelte';
|
package/dist/types.d.ts
CHANGED
|
@@ -68,7 +68,7 @@ export type ArrayType = "string_array" | "number_array" | "boolean_array" | "int
|
|
|
68
68
|
/**
|
|
69
69
|
* Special types for rich content.
|
|
70
70
|
*/
|
|
71
|
-
export type RichType = "
|
|
71
|
+
export type RichType = "annotated_text";
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
74
|
* Node reference types for linking to other nodes.
|
|
@@ -88,7 +88,7 @@ export type PropertyType = PrimitiveType | ReferenceType;
|
|
|
88
88
|
/**
|
|
89
89
|
* Document schema primitive types - all possible property types in document schemas.
|
|
90
90
|
*/
|
|
91
|
-
export type DocumentSchemaPrimitive = "string" | "number" | "boolean" | "integer" | "datetime" | "string_array" | "number_array" | "boolean_array" | "integer_array" | "
|
|
91
|
+
export type DocumentSchemaPrimitive = "string" | "number" | "boolean" | "integer" | "datetime" | "string_array" | "number_array" | "boolean_array" | "integer_array" | "annotated_text" | "node" | "node_array";
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
94
|
* Maps document schema types to their JavaScript runtime types.
|
|
@@ -103,7 +103,7 @@ export type DocumentSchemaValueToJs<T> =
|
|
|
103
103
|
T extends "number_array" ? Array<number> :
|
|
104
104
|
T extends "boolean_array" ? Array<boolean> :
|
|
105
105
|
T extends "integer_array" ? Array<number> :
|
|
106
|
-
T extends "
|
|
106
|
+
T extends "annotated_text" ? AnnotatedText :
|
|
107
107
|
T extends "node" ? string :
|
|
108
108
|
T extends "node_array" ? Array<string> :
|
|
109
109
|
never;
|
|
@@ -112,15 +112,24 @@ export type DocumentSchemaValueToJs<T> =
|
|
|
112
112
|
* Converts a document node schema definition to its inferred JS type.
|
|
113
113
|
* Handles the {type: "..."} wrapper structure used in document schemas.
|
|
114
114
|
*/
|
|
115
|
-
export type DocumentNodeToJs<S extends
|
|
116
|
-
{ id: string, type: string } & { [K in keyof S]: DocumentSchemaValueToJs<S[K]["type"]> };
|
|
115
|
+
export type DocumentNodeToJs<S extends NodeSchema> =
|
|
116
|
+
{ id: string, type: string } & { [K in keyof S["properties"]]: DocumentSchemaValueToJs<S["properties"][K]["type"]> };
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
|
-
* A property that stores
|
|
119
|
+
* A property that stores an annotated text with required allow_newlines setting.
|
|
120
|
+
*/
|
|
121
|
+
export type AnnotatedTextProperty = {
|
|
122
|
+
type: 'annotated_text';
|
|
123
|
+
node_types?: string[];
|
|
124
|
+
allow_newlines: boolean;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* A property that stores a primitive value (excluding annotated_text).
|
|
120
129
|
*/
|
|
121
130
|
export type PrimitiveProperty = {
|
|
122
|
-
[K in PrimitiveType]: { type: K }
|
|
123
|
-
}[PrimitiveType];
|
|
131
|
+
[K in Exclude<PrimitiveType, 'annotated_text'>]: { type: K }
|
|
132
|
+
}[Exclude<PrimitiveType, 'annotated_text'>];
|
|
124
133
|
|
|
125
134
|
/**
|
|
126
135
|
* A property that stores a reference to a single node.
|
|
@@ -143,13 +152,36 @@ export type NodeArrayProperty = {
|
|
|
143
152
|
/**
|
|
144
153
|
* Union type for all possible property definitions.
|
|
145
154
|
*/
|
|
146
|
-
export type PropertyDefinition = PrimitiveProperty | NodeProperty | NodeArrayProperty;
|
|
155
|
+
export type PropertyDefinition = PrimitiveProperty | AnnotatedTextProperty | NodeProperty | NodeArrayProperty;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Node kind values for different types of content nodes
|
|
159
|
+
*/
|
|
160
|
+
export type NodeKind = 'document' | 'block' | 'text' | 'annotation';
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Schema for text nodes - must have a content property of type annotated_text
|
|
164
|
+
*/
|
|
165
|
+
export type TextNodeSchema = {
|
|
166
|
+
kind: 'text';
|
|
167
|
+
properties: {
|
|
168
|
+
content: AnnotatedTextProperty;
|
|
169
|
+
} & Record<string, PropertyDefinition>;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Schema for non-text nodes
|
|
174
|
+
*/
|
|
175
|
+
export type NonTextNodeSchema = {
|
|
176
|
+
kind: 'document' | 'block' | 'annotation';
|
|
177
|
+
properties: Record<string, PropertyDefinition>;
|
|
178
|
+
};
|
|
147
179
|
|
|
148
180
|
/**
|
|
149
181
|
* A node schema defines the structure of a specific node type.
|
|
150
|
-
*
|
|
182
|
+
* Contains a kind and properties object that maps property names to their definitions.
|
|
151
183
|
*/
|
|
152
|
-
export type NodeSchema =
|
|
184
|
+
export type NodeSchema = TextNodeSchema | NonTextNodeSchema;
|
|
153
185
|
|
|
154
186
|
/**
|
|
155
187
|
* A document schema defines all node types available in a document.
|
|
@@ -174,9 +206,9 @@ export type SerializedNode = {
|
|
|
174
206
|
export type SerializedDocument = SerializedNode[];
|
|
175
207
|
|
|
176
208
|
/**
|
|
177
|
-
* Props for the
|
|
209
|
+
* Props for the AnnotatedTextProperty component
|
|
178
210
|
*/
|
|
179
|
-
export type
|
|
211
|
+
export type AnnotatedTextPropertyProps = {
|
|
180
212
|
/** The full path to the property */
|
|
181
213
|
path: DocumentPath;
|
|
182
214
|
/** The `class` attribute on the content element */
|
|
@@ -239,26 +271,39 @@ export type SveditProps = {
|
|
|
239
271
|
editable?: boolean,
|
|
240
272
|
/** The path to the root element (e.g. ['page_1']) */
|
|
241
273
|
path: DocumentPath,
|
|
242
|
-
/** The `class` attribute on the
|
|
274
|
+
/** The `class` attribute on the canvas element */
|
|
243
275
|
class?: string;
|
|
276
|
+
/** The `autocapitalize` attribute on the canvas element */
|
|
277
|
+
autocapitalize?: 'on' | 'off';
|
|
278
|
+
/** The `spellcheck` attribute on the canvas element */
|
|
279
|
+
spellcheck?: 'true' | 'false';
|
|
244
280
|
};
|
|
245
281
|
|
|
246
282
|
/**
|
|
247
283
|
* Represents an annotation in an annotated string
|
|
248
|
-
* Format: [start_offset, end_offset, type, options?]
|
|
249
284
|
*/
|
|
250
|
-
export type Annotation =
|
|
285
|
+
export type Annotation = {
|
|
286
|
+
start_offset: number;
|
|
287
|
+
end_offset: number;
|
|
288
|
+
node_id: NodeId;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Represents an annotated text with text and annotations
|
|
293
|
+
*/
|
|
294
|
+
export type AnnotatedText = {
|
|
295
|
+
text: string;
|
|
296
|
+
annotations: Array<Annotation>;
|
|
297
|
+
};
|
|
251
298
|
|
|
252
299
|
/**
|
|
253
300
|
* Represents a fragment of annotated text content
|
|
254
301
|
*/
|
|
255
|
-
export type AnnotationFragment = {
|
|
256
|
-
/**
|
|
257
|
-
|
|
302
|
+
export type AnnotationFragment = string | {
|
|
303
|
+
/** NodeId that has annotation type and details */
|
|
304
|
+
node: any;
|
|
258
305
|
/** The text content of the annotation */
|
|
259
306
|
content: string;
|
|
260
307
|
/** Index of the annotation in the original array */
|
|
261
308
|
annotation_index: number;
|
|
262
|
-
/** Optional properties from the annotation's options object */
|
|
263
|
-
data?: any;
|
|
264
309
|
};
|