svedit 0.4.0 → 0.5.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 +4 -3
- package/dist/Document.svelte.d.ts +20 -4
- package/dist/Document.svelte.d.ts.map +1 -1
- package/dist/Document.svelte.js +74 -37
- package/dist/Svedit.svelte +239 -192
- package/dist/Svedit.svelte.d.ts.map +1 -1
- package/dist/Transaction.svelte.d.ts +12 -15
- package/dist/Transaction.svelte.d.ts.map +1 -1
- package/dist/Transaction.svelte.js +137 -27
- package/dist/commands.svelte.d.ts.map +1 -1
- package/dist/commands.svelte.js +1 -6
- package/dist/util.d.ts +1 -0
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +34 -0
- package/package.json +21 -21
- package/src/lib/Document.svelte.js +74 -37
- package/src/lib/Svedit.svelte +239 -192
- package/src/lib/Transaction.svelte.js +137 -27
- package/src/lib/commands.svelte.js +1 -6
- package/src/lib/util.js +34 -0
package/dist/Svedit.svelte
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { setContext } from 'svelte';
|
|
3
3
|
import { snake_to_pascal, get_char_length, utf16_to_char_offset, char_to_utf16_offset, get_char_at, char_slice } from './util.js';
|
|
4
|
-
import { break_text_node,
|
|
4
|
+
import { break_text_node, insert_default_node, select_all } from './commands.svelte.js';
|
|
5
|
+
|
|
6
|
+
/** @import {
|
|
7
|
+
* SveditProps,
|
|
8
|
+
* DocumentPath,
|
|
9
|
+
* Selection,
|
|
10
|
+
* TextSelection,
|
|
11
|
+
* NodeSelection,
|
|
12
|
+
* PropertySelection,
|
|
13
|
+
* NodeId
|
|
14
|
+
* } from './types.d.ts';
|
|
15
|
+
*/
|
|
5
16
|
|
|
6
|
-
/** @import { SveditProps, DocumentPath, Selection, TextSelection, NodeSelection, PropertySelection, NodeId } from './types.d.ts'; */
|
|
7
17
|
/** @type {SveditProps} */
|
|
8
18
|
let {
|
|
9
19
|
doc,
|
|
@@ -21,9 +31,7 @@
|
|
|
21
31
|
|
|
22
32
|
// Track temporary disabled onkeydown events (e.g. during character composition)
|
|
23
33
|
let skip_onkeydown = false;
|
|
24
|
-
|
|
25
34
|
let is_composing = $state(false);
|
|
26
|
-
|
|
27
35
|
let input_selection = undefined;
|
|
28
36
|
|
|
29
37
|
// Detect Chrome on desktop (not mobile) - only available in browser
|
|
@@ -183,73 +191,18 @@
|
|
|
183
191
|
event.stopPropagation();
|
|
184
192
|
}
|
|
185
193
|
|
|
186
|
-
|
|
187
194
|
if (event.inputType === 'deleteContentBackward') {
|
|
188
|
-
|
|
189
|
-
// For property selections, clear the property value only if it's not already falsy
|
|
190
|
-
if (doc.get(doc.selection.path)) {
|
|
191
|
-
const tr = doc.tr;
|
|
192
|
-
tr.set(doc.selection.path, '');
|
|
193
|
-
doc.apply(tr);
|
|
194
|
-
}
|
|
195
|
-
} else {
|
|
196
|
-
if (doc.selection?.type === 'text' && doc.selection?.anchor_offset === 0 && doc.selection?.focus_offset === 0) {
|
|
197
|
-
const tr = doc.tr;
|
|
198
|
-
join_text_node(tr);
|
|
199
|
-
doc.apply(tr);
|
|
200
|
-
} else {
|
|
201
|
-
doc.apply(doc.tr.delete_selection());
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
195
|
+
doc.apply(doc.tr.delete_selection('backward'));
|
|
205
196
|
event.preventDefault();
|
|
206
197
|
event.stopPropagation();
|
|
207
198
|
}
|
|
208
199
|
|
|
209
200
|
if (event.inputType === 'deleteContentForward') {
|
|
210
|
-
|
|
211
|
-
if (doc.selection?.type === 'text' && doc.selection?.anchor_offset === doc.selection?.focus_offset) {
|
|
212
|
-
const text_content = doc.get(doc.selection.path).text;
|
|
213
|
-
const text_length = get_char_length(text_content);
|
|
214
|
-
|
|
215
|
-
if (doc.selection.focus_offset < text_length) {
|
|
216
|
-
// There's a character after the cursor - select and delete it
|
|
217
|
-
const tr = doc.tr;
|
|
218
|
-
tr.set_selection({
|
|
219
|
-
type: 'text',
|
|
220
|
-
path: [...doc.selection.path],
|
|
221
|
-
anchor_offset: doc.selection.focus_offset,
|
|
222
|
-
focus_offset: doc.selection.focus_offset + 1
|
|
223
|
-
});
|
|
224
|
-
tr.delete_selection();
|
|
225
|
-
doc.apply(tr);
|
|
226
|
-
} else {
|
|
227
|
-
// At end of text - try to join with next text node
|
|
228
|
-
const tr = doc.tr;
|
|
229
|
-
const node_index = parseInt(String(doc.selection.path.at(-2)), 10);
|
|
230
|
-
const successor_node = doc.get([...doc.selection.path.slice(0, -2), node_index + 1]);
|
|
231
|
-
|
|
232
|
-
// Check if next node is a text node
|
|
233
|
-
if (successor_node && doc.kind(successor_node) === 'text') {
|
|
234
|
-
// Set selection to beginning of next text node
|
|
235
|
-
tr.set_selection({
|
|
236
|
-
type: 'text',
|
|
237
|
-
path: [...doc.selection.path.slice(0, -2), node_index + 1, 'content'],
|
|
238
|
-
anchor_offset: 0,
|
|
239
|
-
focus_offset: 0
|
|
240
|
-
});
|
|
241
|
-
// Use join_text_node to merge with previous
|
|
242
|
-
join_text_node(tr);
|
|
243
|
-
doc.apply(tr);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
201
|
+
doc.apply(doc.tr.delete_selection('forward'));
|
|
248
202
|
event.preventDefault();
|
|
249
203
|
event.stopPropagation();
|
|
250
204
|
}
|
|
251
205
|
|
|
252
|
-
|
|
253
206
|
// We remember the "input selection", because in some mobile browsers (Samsung Android) the onbeforeinput event
|
|
254
207
|
// is fired on a different selection than the input event.
|
|
255
208
|
input_selection = { ...doc.selection };
|
|
@@ -383,29 +336,47 @@ ${fallback_html}`;
|
|
|
383
336
|
* @param {Object} node - Node object
|
|
384
337
|
* @returns {string} HTML representation
|
|
385
338
|
*/
|
|
386
|
-
function
|
|
339
|
+
function default_node_html_exporter(node, doc, html_exporters) {
|
|
387
340
|
let html = '';
|
|
388
|
-
|
|
341
|
+
const node_schema = doc.schema[node.type];
|
|
389
342
|
|
|
390
343
|
for (const [prop_name, prop_value] of Object.entries(node)) {
|
|
391
344
|
if (prop_name === 'id' || prop_name === 'type') continue;
|
|
392
|
-
|
|
345
|
+
const property_definition = node_schema.properties[prop_name];
|
|
393
346
|
// Check if this is an annotated_text property (object with text property)
|
|
394
|
-
if (
|
|
347
|
+
if (property_definition.type === 'annotated_text') {
|
|
395
348
|
const text_content = prop_value.text;
|
|
396
349
|
if (text_content.trim()) {
|
|
397
|
-
html += `<p>${text_content}</p>\n`;
|
|
398
|
-
|
|
350
|
+
html += `<p>${text_content.trim()}</p>\n`;
|
|
351
|
+
}
|
|
352
|
+
} else if (property_definition.type === 'node_array') {
|
|
353
|
+
for (const child_id of prop_value) {
|
|
354
|
+
const child = doc.get(child_id);
|
|
355
|
+
const child_exporter = html_exporters[child.type] || default_node_html_exporter;
|
|
356
|
+
html += child_exporter(child, doc, html_exporters);
|
|
399
357
|
}
|
|
400
358
|
}
|
|
401
359
|
}
|
|
402
360
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
361
|
+
return html;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function default_node_plain_text_exporter(node) {
|
|
365
|
+
let plain_text = '';
|
|
366
|
+
|
|
367
|
+
for (const [prop_name, prop_value] of Object.entries(node)) {
|
|
368
|
+
if (prop_name === 'id' || prop_name === 'type') continue;
|
|
369
|
+
|
|
370
|
+
// Check if this is an annotated_text property (object with text property)
|
|
371
|
+
if (typeof prop_value === 'object' && prop_value !== null && typeof prop_value.text === 'string') {
|
|
372
|
+
const text_content = prop_value.text;
|
|
373
|
+
if (text_content.trim()) {
|
|
374
|
+
plain_text += `${text_content.trim()}\n\n`;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
406
377
|
}
|
|
407
378
|
|
|
408
|
-
return
|
|
379
|
+
return plain_text;
|
|
409
380
|
}
|
|
410
381
|
|
|
411
382
|
/**
|
|
@@ -421,14 +392,22 @@ ${fallback_html}`;
|
|
|
421
392
|
|
|
422
393
|
if (html_exporters[node.type]) {
|
|
423
394
|
// Use custom exporter for this node type
|
|
424
|
-
html += html_exporters[node.type](node, doc);
|
|
395
|
+
html += html_exporters[node.type](node, doc, html_exporters);
|
|
425
396
|
} else {
|
|
426
397
|
// Use default exporter
|
|
427
|
-
html +=
|
|
398
|
+
html += default_node_html_exporter(node, doc, html_exporters);
|
|
428
399
|
}
|
|
429
400
|
}
|
|
401
|
+
return html;
|
|
402
|
+
}
|
|
430
403
|
|
|
431
|
-
|
|
404
|
+
function export_plain_text(nodes) {
|
|
405
|
+
let plain_text = '';
|
|
406
|
+
|
|
407
|
+
for (const node of nodes) {
|
|
408
|
+
plain_text += default_node_plain_text_exporter(node);
|
|
409
|
+
}
|
|
410
|
+
return plain_text.trim();
|
|
432
411
|
}
|
|
433
412
|
|
|
434
413
|
/**
|
|
@@ -464,18 +443,27 @@ ${fallback_html}`;
|
|
|
464
443
|
event.preventDefault();
|
|
465
444
|
event.stopPropagation();
|
|
466
445
|
|
|
467
|
-
let plain_text, html;
|
|
446
|
+
let plain_text, annotated_text, html;
|
|
468
447
|
|
|
469
448
|
if (doc.selection?.type === 'text') {
|
|
470
449
|
plain_text = doc.get_selected_plain_text();
|
|
471
|
-
|
|
450
|
+
annotated_text = doc.get_selected_annotated_text();
|
|
451
|
+
const fallback_html = `<span>${annotated_text.text}</span>`;
|
|
452
|
+
|
|
453
|
+
console.log('Text copy:', {
|
|
454
|
+
annotated_text,
|
|
455
|
+
plain_text,
|
|
456
|
+
html,
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
html = create_svedit_html_format(annotated_text, fallback_html);
|
|
472
460
|
} else if (doc.selection?.type === 'node') {
|
|
473
461
|
const selected_nodes = doc.get_selected_nodes();
|
|
474
462
|
const { nodes, main_nodes } = prepare_copy_payload(selected_nodes);
|
|
475
463
|
|
|
476
464
|
const json_data = { nodes, main_nodes };
|
|
477
465
|
|
|
478
|
-
console.log('
|
|
466
|
+
console.log('Node copy:', {
|
|
479
467
|
selected_nodes,
|
|
480
468
|
nodes,
|
|
481
469
|
total_nodes: Object.keys(nodes).length,
|
|
@@ -488,30 +476,30 @@ ${fallback_html}`;
|
|
|
488
476
|
|
|
489
477
|
// Create HTML with embedded svedit data
|
|
490
478
|
html = create_svedit_html_format(json_data, fallback_html);
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
479
|
+
// Generate plain text representation
|
|
480
|
+
plain_text = export_plain_text(selected_node_objects);
|
|
481
|
+
} else if (doc.selection?.type === 'property') {
|
|
482
|
+
const property_definition = doc.inspect(doc.selection.path);
|
|
483
|
+
const value = doc.get(doc.selection.path);
|
|
484
|
+
const json_data = {
|
|
485
|
+
kind: 'property',
|
|
486
|
+
name: property_definition.name,
|
|
487
|
+
type: property_definition.type,
|
|
488
|
+
value,
|
|
489
|
+
};
|
|
490
|
+
console.log('Property copy:', json_data);
|
|
491
|
+
html = create_svedit_html_format(json_data, `<span>${value}</span>`);
|
|
492
|
+
plain_text = String(value);
|
|
499
493
|
}
|
|
500
494
|
|
|
501
|
-
//
|
|
502
|
-
|
|
503
|
-
'text/plain'
|
|
504
|
-
'text/html'
|
|
505
|
-
};
|
|
506
|
-
|
|
507
|
-
const clipboard_item_raw = new ClipboardItem(data);
|
|
508
|
-
|
|
509
|
-
// Write to clipboard
|
|
510
|
-
navigator.clipboard.write([clipboard_item_raw]).then(() => {
|
|
495
|
+
// Write to clipboard using event.clipboardData
|
|
496
|
+
try {
|
|
497
|
+
event.clipboardData?.setData('text/plain', plain_text || '');
|
|
498
|
+
event.clipboardData?.setData('text/html', html || '');
|
|
511
499
|
console.log('Data copied to clipboard successfully');
|
|
512
|
-
}
|
|
500
|
+
} catch (err) {
|
|
513
501
|
console.error('Failed to copy data: ', err);
|
|
514
|
-
}
|
|
502
|
+
}
|
|
515
503
|
|
|
516
504
|
if (delete_selection) {
|
|
517
505
|
doc.apply(doc.tr.delete_selection());
|
|
@@ -522,87 +510,163 @@ ${fallback_html}`;
|
|
|
522
510
|
oncopy(event, true);
|
|
523
511
|
}
|
|
524
512
|
|
|
525
|
-
async function onpaste(event) {
|
|
526
|
-
// Only handle paste events if focus is within the canvas
|
|
527
|
-
if (!canvas_ref?.contains(document.activeElement)) return;
|
|
528
|
-
|
|
529
|
-
event.preventDefault();
|
|
530
|
-
const clipboardItems = await navigator.clipboard.read();
|
|
531
513
|
|
|
532
|
-
|
|
514
|
+
/**
|
|
515
|
+
* Attempts to paste JSON data as a node at the current selection.
|
|
516
|
+
*
|
|
517
|
+
* @param {string|object} pasted_json - The JSON data to paste, either as a string or parsed object
|
|
518
|
+
* @param {Selection} [selection] - Optional selection (node cursor) where the payload should be pasted
|
|
519
|
+
* @returns {boolean} True if the paste operation was successful, false otherwise
|
|
520
|
+
*/
|
|
521
|
+
function try_node_paste(pasted_json, selection) {
|
|
522
|
+
const { nodes, main_nodes } = pasted_json;
|
|
533
523
|
|
|
534
|
-
//
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
} catch (e) {
|
|
540
|
-
console.log('No HTML format available or failed to extract svedit data:', e);
|
|
541
|
-
pasted_json = undefined;
|
|
524
|
+
// NOTE: At this point, nodes contains a subgraph from the copy
|
|
525
|
+
// with original ids.
|
|
526
|
+
let tr = doc.tr;
|
|
527
|
+
if (selection) {
|
|
528
|
+
tr.set_selection(selection);
|
|
542
529
|
}
|
|
543
530
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
531
|
+
// We can safely assume we're dealing with a node_array property
|
|
532
|
+
const property_definition = doc.inspect(tr.selection.path);
|
|
533
|
+
const first_compatible_text_node_type = property_definition.node_types.find(type => doc.kind({ type }) === 'text');
|
|
534
|
+
|
|
535
|
+
const nodes_to_insert = [];
|
|
536
|
+
let rejected = false;
|
|
537
|
+
for (const node_id of main_nodes) {
|
|
538
|
+
const node = nodes[node_id];
|
|
539
|
+
if (!property_definition.node_types.includes(node.type)) {
|
|
540
|
+
// Incompatible node type detected
|
|
541
|
+
if (doc.kind(node) === 'text' && first_compatible_text_node_type) {
|
|
542
|
+
const new_node_id = tr.build('the_node', {
|
|
543
|
+
the_node: {
|
|
544
|
+
id: 'the_node',
|
|
545
|
+
type: first_compatible_text_node_type,
|
|
546
|
+
content: node.content,
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
nodes_to_insert.push(new_node_id);
|
|
550
|
+
} else {
|
|
551
|
+
console.log(`rejected ${node.type}. Only ${property_definition.node_types.join(', ')} allowed.`);
|
|
552
|
+
rejected = true;
|
|
553
|
+
break;
|
|
555
554
|
}
|
|
555
|
+
} else {
|
|
556
|
+
const new_node_id = tr.build(node_id, nodes);
|
|
557
|
+
nodes_to_insert.push(new_node_id);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
556
560
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
const prop_type = doc.schema[node.type]?.properties?.[property]?.type;
|
|
568
|
-
|
|
569
|
-
if (prop_type === 'node_array' && Array.isArray(value)) {
|
|
570
|
-
new_node[property] = value.map(ref_id => id_mapping[ref_id] || ref_id);
|
|
571
|
-
} else if (prop_type === 'node' && typeof value === 'string') {
|
|
572
|
-
new_node[property] = id_mapping[value] || value;
|
|
573
|
-
} else if (prop_type === 'annotated_text') {
|
|
574
|
-
const annotations = value.annotations.map(annotation => {
|
|
575
|
-
const {start_offset, end_offset, node_id} = annotation;
|
|
576
|
-
return {start_offset, end_offset, node_id: id_mapping[node_id] || node_id};
|
|
577
|
-
});
|
|
578
|
-
new_node[property] = {text: value.text, annotations};
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
updated_nodes[new_node.id] = new_node;
|
|
561
|
+
if (!rejected) {
|
|
562
|
+
tr.insert_nodes(nodes_to_insert);
|
|
563
|
+
doc.apply(tr);
|
|
564
|
+
return true;
|
|
565
|
+
} else {
|
|
566
|
+
if (tr.selection.path.length >= 2) {
|
|
567
|
+
const next_node_insert_cursor = doc.get_next_node_insert_cursor(tr.selection);
|
|
568
|
+
if (next_node_insert_cursor) {
|
|
569
|
+
try_node_paste(pasted_json, next_node_insert_cursor);
|
|
583
570
|
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
584
575
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
576
|
+
async function onpaste(event) {
|
|
577
|
+
// Only handle paste events if focus is within the canvas
|
|
578
|
+
if (!canvas_ref?.contains(document.activeElement)) return;
|
|
579
|
+
event.preventDefault();
|
|
589
580
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
581
|
+
let plain_text, pasted_json, pasted_images = [];
|
|
582
|
+
|
|
583
|
+
// NOTE: For some reason, await navigator.clipboard.read()
|
|
584
|
+
const clipboard_items = event.clipboardData?.items || [];
|
|
585
|
+
for (const item of clipboard_items || []) {
|
|
586
|
+
if (item.type.startsWith("image/")) {
|
|
587
|
+
const blob = item.getAsFile();
|
|
588
|
+
const data_url = URL.createObjectURL(blob);
|
|
589
|
+
pasted_images.push({
|
|
590
|
+
blob,
|
|
591
|
+
data_url,
|
|
592
|
+
type: item.type,
|
|
593
|
+
size: blob.size
|
|
594
|
+
});
|
|
594
595
|
}
|
|
596
|
+
}
|
|
595
597
|
|
|
596
|
-
|
|
598
|
+
if (pasted_images.length > 0) {
|
|
599
|
+
pasted_json = await doc.config.handle_image_paste(doc, pasted_images);
|
|
600
|
+
console.log('pasted_json_after_image_paste', pasted_json);
|
|
601
|
+
// NOTE: If no pasted_json is returned from the custom handler, we assume that content creation has been
|
|
602
|
+
// handled inside handle_image_paste already.
|
|
603
|
+
if (!pasted_json) return;
|
|
597
604
|
} else {
|
|
598
|
-
//
|
|
605
|
+
// First try to extract svedit data from HTML format
|
|
606
|
+
try {
|
|
607
|
+
const html_content = event.clipboardData?.getData('text/html');
|
|
608
|
+
if (html_content) {
|
|
609
|
+
pasted_json = extract_svedit_data_from_html(html_content);
|
|
610
|
+
}
|
|
611
|
+
} catch (e) {
|
|
612
|
+
console.log('No HTML format available or failed to extract svedit data:', e);
|
|
613
|
+
pasted_json = undefined;
|
|
614
|
+
}
|
|
615
|
+
|
|
599
616
|
try {
|
|
600
|
-
|
|
601
|
-
const plain_text = await plain_text_blob.text();
|
|
602
|
-
doc.apply(doc.tr.insert_text(plain_text));
|
|
617
|
+
plain_text = event.clipboardData?.getData('text/plain');
|
|
603
618
|
} catch (e) {
|
|
604
619
|
console.error('Failed to paste any content:', e);
|
|
605
620
|
}
|
|
621
|
+
|
|
622
|
+
// Try to contruct a node payload from plain text when applicable
|
|
623
|
+
if (!pasted_json && typeof plain_text === 'string') {
|
|
624
|
+
const plain_text_fragments = plain_text.split('\n\n').map(fragment => fragment.trim()).filter(Boolean);
|
|
625
|
+
if (plain_text_fragments.length > 1) {
|
|
626
|
+
pasted_json = {
|
|
627
|
+
main_nodes: [],
|
|
628
|
+
nodes: [],
|
|
629
|
+
};
|
|
630
|
+
for (let i = 0; i < plain_text_fragments.length; i++) {
|
|
631
|
+
const fragment = plain_text_fragments[i];
|
|
632
|
+
pasted_json.nodes["fragment_" + i] = {
|
|
633
|
+
id: "fragment_" + i,
|
|
634
|
+
type: 'text',
|
|
635
|
+
content: {
|
|
636
|
+
text: fragment,
|
|
637
|
+
annotations: []
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
pasted_json.main_nodes.push("fragment_" + i);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (pasted_json?.main_nodes && doc.selection?.type === 'node') {
|
|
647
|
+
// Paste nodes at a node selection
|
|
648
|
+
try_node_paste(pasted_json);
|
|
649
|
+
} else if (pasted_json?.kind === 'property' && doc.selection?.type === 'property') {
|
|
650
|
+
const property_definition = doc.inspect(doc.selection.path);
|
|
651
|
+
if (property_definition.type === pasted_json.type) {
|
|
652
|
+
doc.apply(doc.tr.set(doc.selection.path, pasted_json.value));
|
|
653
|
+
}
|
|
654
|
+
} else if (doc.selection?.type === 'text' && pasted_json?.text) {
|
|
655
|
+
// Paste text at a text selection
|
|
656
|
+
doc.apply(doc.tr.insert_text(pasted_json.text, pasted_json.annotations, pasted_json.nodes));
|
|
657
|
+
} else if (doc.selection?.type === 'text' && pasted_json?.main_nodes?.length === 1 && doc.kind(pasted_json?.nodes[pasted_json.main_nodes[0]]) === 'text') {
|
|
658
|
+
// Paste a single text node, at a text cursor
|
|
659
|
+
const text_property = pasted_json.nodes[pasted_json.main_nodes[0]].content;
|
|
660
|
+
doc.apply(doc.tr.insert_text(text_property.text, text_property.annotations, pasted_json.nodes));
|
|
661
|
+
} else if (['text', 'property'].includes(doc.selection?.type) && pasted_json?.nodes) {
|
|
662
|
+
// Paste nodes at a text or property selection by finding the next valid insert cursor
|
|
663
|
+
const next_node_insert_cursor = doc.get_next_node_insert_cursor(doc.selection);
|
|
664
|
+
try_node_paste(pasted_json, next_node_insert_cursor);
|
|
665
|
+
} else if (typeof plain_text === 'string') {
|
|
666
|
+
// External paste: Fallback to plain text when no svedit data is found
|
|
667
|
+
doc.apply(doc.tr.insert_text(plain_text.trim()));
|
|
668
|
+
} else {
|
|
669
|
+
console.log('Could not paste.');
|
|
606
670
|
}
|
|
607
671
|
}
|
|
608
672
|
|
|
@@ -700,6 +764,7 @@ ${fallback_html}`;
|
|
|
700
764
|
) {
|
|
701
765
|
const node = doc.layout_node;
|
|
702
766
|
const layout_count = doc.config.node_layouts[node.type];
|
|
767
|
+
|
|
703
768
|
if (layout_count > 1 && node?.layout) {
|
|
704
769
|
const prev_layout = ((node.layout - 2 + layout_count) % layout_count) + 1;
|
|
705
770
|
const tr = doc.tr;
|
|
@@ -713,16 +778,13 @@ ${fallback_html}`;
|
|
|
713
778
|
(e.key === 'ArrowDown' && e.altKey && e.ctrlKey && e.shiftKey && doc.layout_node )
|
|
714
779
|
) {
|
|
715
780
|
const node = doc.layout_node;
|
|
716
|
-
|
|
717
781
|
if (doc.selection.type !== 'node') {
|
|
718
782
|
doc.select_parent();
|
|
719
783
|
}
|
|
720
784
|
const old_selection = { ...doc.selection };
|
|
721
|
-
|
|
722
785
|
const node_array_schema = doc.inspect(doc.selection.path);
|
|
723
786
|
// If we are not dealing with a node selection in a container, return
|
|
724
787
|
if (node_array_schema.type !== 'node_array') return;
|
|
725
|
-
|
|
726
788
|
const current_type_index = node_array_schema.node_types.indexOf(node.type);
|
|
727
789
|
const next_type_index = (current_type_index + 1) % node_array_schema.node_types.length;
|
|
728
790
|
const next_type = node_array_schema.node_types[next_type_index];
|
|
@@ -736,19 +798,14 @@ ${fallback_html}`;
|
|
|
736
798
|
(e.key === 'ArrowUp' && e.altKey && e.ctrlKey && e.shiftKey && doc.layout_node)
|
|
737
799
|
) {
|
|
738
800
|
const node = doc.layout_node;
|
|
739
|
-
|
|
740
801
|
if (doc.selection.type !== 'node') {
|
|
741
802
|
doc.select_parent();
|
|
742
803
|
}
|
|
743
|
-
|
|
744
|
-
// if (selection.type !== 'node') return;
|
|
745
804
|
const old_selection = { ...doc.selection };
|
|
746
805
|
const node_array_schema = doc.inspect(doc.selection.path);
|
|
747
806
|
// If we are not dealing with a node selection in a container, return
|
|
748
807
|
if (node_array_schema.type !== 'node_array') return;
|
|
749
|
-
|
|
750
808
|
const current_type_index = node_array_schema.node_types.indexOf(node.type);
|
|
751
|
-
// return (current_layout_number - 1 + layout_count) % layout_count;
|
|
752
809
|
const prev_type_index = (current_type_index - 1 + node_array_schema.node_types.length) % node_array_schema.node_types.length;
|
|
753
810
|
const prev_type = node_array_schema.node_types[prev_type_index];
|
|
754
811
|
const tr = doc.tr;
|
|
@@ -756,7 +813,6 @@ ${fallback_html}`;
|
|
|
756
813
|
tr.set_selection(old_selection);
|
|
757
814
|
doc.apply(tr);
|
|
758
815
|
e.preventDefault();
|
|
759
|
-
|
|
760
816
|
} else if (e.key === 'a' && (e.metaKey || e.ctrlKey)) {
|
|
761
817
|
const tr = doc.tr;
|
|
762
818
|
if (select_all(tr)) {
|
|
@@ -812,16 +868,10 @@ ${fallback_html}`;
|
|
|
812
868
|
const span_length = Math.abs(selection.focus_offset - selection.anchor_offset);
|
|
813
869
|
|
|
814
870
|
if (is_collapsed) {
|
|
815
|
-
//
|
|
871
|
+
// Always insert default node on ENTER
|
|
816
872
|
const tr = doc.tr;
|
|
817
873
|
insert_default_node(tr);
|
|
818
874
|
doc.apply(tr);
|
|
819
|
-
// if (insert_default_node(tr)) {
|
|
820
|
-
// doc.apply(tr);
|
|
821
|
-
// } else {
|
|
822
|
-
// // Fall back to focusing toolbar when multiple types are available
|
|
823
|
-
// focus_toolbar();
|
|
824
|
-
// }
|
|
825
875
|
} else if (span_length === 1) {
|
|
826
876
|
focus_toolbar();
|
|
827
877
|
}
|
|
@@ -1236,7 +1286,7 @@ ${fallback_html}`;
|
|
|
1236
1286
|
(is_backward ? focus_node : anchor_node).scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
|
1237
1287
|
}, 0);
|
|
1238
1288
|
} else {
|
|
1239
|
-
console.log('
|
|
1289
|
+
console.log('No container element found!');
|
|
1240
1290
|
}
|
|
1241
1291
|
}
|
|
1242
1292
|
|
|
@@ -1245,7 +1295,6 @@ ${fallback_html}`;
|
|
|
1245
1295
|
// The element that holds the property
|
|
1246
1296
|
const el = canvas_ref.querySelector(`[data-path="${selection.path.join('.')}"][data-type="property"]`);
|
|
1247
1297
|
const cursor_trap_selectable = el.querySelector('.svedit-selectable');
|
|
1248
|
-
|
|
1249
1298
|
const range = window.document.createRange();
|
|
1250
1299
|
const dom_selection = window.getSelection();
|
|
1251
1300
|
|
|
@@ -1254,6 +1303,14 @@ ${fallback_html}`;
|
|
|
1254
1303
|
range.collapse(true); // Collapse to start position
|
|
1255
1304
|
dom_selection.removeAllRanges();
|
|
1256
1305
|
dom_selection.addRange(range);
|
|
1306
|
+
|
|
1307
|
+
// Scroll the selection into view
|
|
1308
|
+
setTimeout(() => {
|
|
1309
|
+
const selectedElement = dom_selection.focusNode.parentElement;
|
|
1310
|
+
if (selectedElement) {
|
|
1311
|
+
selectedElement.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
|
1312
|
+
}
|
|
1313
|
+
}, 0);
|
|
1257
1314
|
}
|
|
1258
1315
|
|
|
1259
1316
|
function __render_text_selection() {
|
|
@@ -1313,7 +1370,6 @@ ${fallback_html}`;
|
|
|
1313
1370
|
return true; // Stop iteration
|
|
1314
1371
|
}
|
|
1315
1372
|
}
|
|
1316
|
-
|
|
1317
1373
|
current_offset += node_char_length;
|
|
1318
1374
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
1319
1375
|
for (const child_node of node.childNodes) {
|
|
@@ -1336,16 +1392,6 @@ ${fallback_html}`;
|
|
|
1336
1392
|
for (const child_node of el.childNodes) {
|
|
1337
1393
|
if (process_node(child_node)) break;
|
|
1338
1394
|
}
|
|
1339
|
-
|
|
1340
|
-
// EDGE CASE: cursor at the end of an annotation
|
|
1341
|
-
// TODO: It seems this edge case is handled elsewhere. We keep around the checker for a
|
|
1342
|
-
// while and delete this branch, if we don't see any problems.
|
|
1343
|
-
if (anchor_node && !focus_node && current_offset === end_offset) {
|
|
1344
|
-
alert('EDGE CASE: cursor at the end of an annotation.');
|
|
1345
|
-
// console.log('EDGE CASE: cursor at the end of an annotation');
|
|
1346
|
-
// focus_node = /** @type {HTMLElement | Text} */ (anchor_node.nextSibling || anchor_node);
|
|
1347
|
-
// focus_node_offset = focus_node === anchor_node ? anchor_node.length : 0;
|
|
1348
|
-
}
|
|
1349
1395
|
}
|
|
1350
1396
|
|
|
1351
1397
|
// Set the range if both start and end were found
|
|
@@ -1431,6 +1477,7 @@ ${fallback_html}`;
|
|
|
1431
1477
|
class="svedit-canvas {css_class}"
|
|
1432
1478
|
class:hide-selection={doc.selection?.type === 'node'}
|
|
1433
1479
|
class:node-cursor={doc.selection?.type === 'node' && doc.selection.anchor_offset === doc.selection.focus_offset}
|
|
1480
|
+
class:property-selection={doc.selection?.type === 'property'}
|
|
1434
1481
|
bind:this={canvas_ref}
|
|
1435
1482
|
{oninput}
|
|
1436
1483
|
{onbeforeinput}
|
|
@@ -1475,7 +1522,7 @@ ${fallback_html}`;
|
|
|
1475
1522
|
}
|
|
1476
1523
|
|
|
1477
1524
|
/* When the cursor is in a cursor-trap we never want to see the caret */
|
|
1478
|
-
.svedit-canvas.node-cursor {
|
|
1525
|
+
.svedit-canvas.node-cursor, .svedit-canvas.property-selection {
|
|
1479
1526
|
caret-color: transparent;
|
|
1480
1527
|
}
|
|
1481
1528
|
|
|
@@ -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":";;;;;;;AAk9CA;;eAAqD;iCA97CzC,cAAc"}
|