edytor 5.0.0 → 7.0.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 +3 -3
- package/dist/cloudflare/DocumentRoom.d.ts +77 -2
- package/dist/cloudflare/DocumentRoom.js +37 -1
- package/dist/cloudflare/index.d.ts +1 -1
- package/dist/cloudflare/index.js +1 -1
- package/dist/cloudflare/room/access.d.ts +6 -1
- package/dist/cloudflare/room/access.js +30 -4
- package/dist/cloudflare/room/admission.d.ts +11 -0
- package/dist/cloudflare/room/admission.js +43 -10
- package/dist/cloudflare/room/context.d.ts +73 -76
- package/dist/cloudflare/room/context.js +17 -6
- package/dist/cloudflare/room/forged.d.ts +1 -1
- package/dist/cloudflare/room/forged.js +11 -9
- package/dist/cloudflare/room/frames.d.ts +2 -0
- package/dist/cloudflare/room/frames.js +2 -0
- package/dist/cloudflare/room/presence.js +1 -4
- package/dist/cloudflare/room/reseed.d.ts +93 -0
- package/dist/cloudflare/room/reseed.js +500 -0
- package/dist/cloudflare/room/scheduler.d.ts +1 -1
- package/dist/cloudflare/room/scheduler.js +9 -1
- package/dist/cloudflare/room/shared.d.ts +10 -0
- package/dist/cloudflare/room/shared.js +10 -0
- package/dist/cloudflare/room/storage.d.ts +7 -0
- package/dist/cloudflare/room/storage.js +33 -1
- package/dist/components/Edytor.svelte +18 -3
- package/dist/components/Edytor.svelte.d.ts +10 -1
- package/dist/components/EdytorRoot.svelte +17 -0
- package/dist/components/EdytorRoot.svelte.d.ts +4 -0
- package/dist/components/Suggestion.svelte +1 -1
- package/dist/crdt/attribution/block.d.ts +3 -0
- package/dist/crdt/attribution/block.js +3 -2
- package/dist/crdt/doc/history.js +2 -3
- package/dist/crdt/doc/ops.d.ts +81 -0
- package/dist/crdt/doc/ops.js +1898 -0
- package/dist/crdt/doc/suggest.d.ts +6 -2
- package/dist/crdt/doc/suggest.js +6 -5
- package/dist/crdt/edytor-doc.d.ts +157 -157
- package/dist/crdt/edytor-doc.js +15 -43
- package/dist/crdt/engine-room.d.ts +4 -0
- package/dist/crdt/engine-room.js +6 -0
- package/dist/crdt/flow.d.ts +4 -46
- package/dist/crdt/flow.js +2 -262
- package/dist/crdt/index.d.ts +64 -75
- package/dist/crdt/operations.d.ts +1 -1
- package/dist/crdt/placement/resolve.d.ts +1 -1
- package/dist/crdt/protocol.d.ts +2 -2
- package/dist/crdt/protocol.js +2 -2
- package/dist/crdt/protocols/auth.d.ts +8 -1
- package/dist/crdt/protocols/auth.js +14 -1
- package/dist/crdt/protocols/awareness.js +46 -81
- package/dist/crdt/providers/index.d.ts +11 -22
- package/dist/crdt/providers/index.js +22 -3
- package/dist/crdt/providers/indexeddb.d.ts +6 -10
- package/dist/crdt/providers/indexeddb.js +10 -16
- package/dist/crdt/providers/room.d.ts +26 -0
- package/dist/crdt/providers/room.js +41 -12
- package/dist/crdt/providers/websocket.d.ts +10 -14
- package/dist/crdt/providers/websocket.js +43 -23
- package/dist/crdt/rangeDelete.d.ts +2 -47
- package/dist/crdt/rangeDelete.js +1 -169
- package/dist/crdt/reseed.d.ts +127 -0
- package/dist/crdt/reseed.js +533 -0
- package/dist/crdt/restore.d.ts +13 -1
- package/dist/crdt/restore.js +8 -3
- package/dist/crdt/schema.d.ts +6 -0
- package/dist/crdt/schema.js +6 -0
- package/dist/crdt/structs.d.ts +8 -11
- package/dist/crdt/structs.js +25 -44
- package/dist/crdt/text/index/build.d.ts +119 -0
- package/dist/crdt/text/index/build.js +2801 -0
- package/dist/crdt/text/index/shared.d.ts +2 -6
- package/dist/crdt/text/index/shared.js +2 -5
- package/dist/crdt/text/model.js +2 -3
- package/dist/crdt/text/runs.d.ts +9 -12
- package/dist/crdt/text/runs.js +2 -165
- package/dist/crdt/vendor/yjs/UPSTREAM.md +101 -5
- package/dist/crdt/vendor/yjs/dts/index.d.ts +1 -1
- package/dist/crdt/vendor/yjs/dts/utils/Transaction.d.ts +10 -10
- package/dist/crdt/vendor/yjs/dts/utils/encoding.d.ts +1 -0
- package/dist/crdt/vendor/yjs/src/index.js +1 -1
- package/dist/crdt/vendor/yjs/src/structs/Item.js +24 -13
- package/dist/crdt/vendor/yjs/src/utils/Transaction.js +12 -10
- package/dist/crdt/vendor/yjs/src/utils/encoding.js +42 -1
- package/dist/events/domTextOffset.d.ts +2 -0
- package/dist/events/domTextOffset.js +8 -0
- package/dist/export/forms.d.ts +50 -50
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/plugins/richtext/RichTextPlugin.svelte +63 -61
- package/dist/plugins/richtext/richTextOperations.js +26 -32
- package/dist/selection/selection.svelte.d.ts +1 -1
- package/dist/selection/selection.svelte.js +87 -106
- package/dist/selection/selection.utils.js +5 -8
- package/dist/session/commands.js +49 -51
- package/dist/session/handles.js +3 -7
- package/dist/surface/attributes.d.ts +3 -3
- package/dist/surface/attributes.js +2 -2
- package/dist/surface/observer.svelte.d.ts +7 -7
- package/dist/surface/observer.svelte.js +225 -336
- package/dist/surface/projector.svelte.js +2 -2
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +7 -0
- package/package.json +1 -1
- package/dist/crdt/doc/capability.d.ts +0 -56
- package/dist/crdt/doc/capability.js +0 -207
- package/dist/crdt/doc/content.d.ts +0 -16
- package/dist/crdt/doc/content.js +0 -77
- package/dist/crdt/doc/delete.d.ts +0 -15
- package/dist/crdt/doc/delete.js +0 -84
- package/dist/crdt/doc/layout.d.ts +0 -9
- package/dist/crdt/doc/layout.js +0 -178
- package/dist/crdt/doc/meta.d.ts +0 -19
- package/dist/crdt/doc/meta.js +0 -198
- package/dist/crdt/doc/moves.d.ts +0 -21
- package/dist/crdt/doc/moves.js +0 -274
- package/dist/crdt/doc/split-merge.d.ts +0 -10
- package/dist/crdt/doc/split-merge.js +0 -179
- package/dist/crdt/doc/steps.d.ts +0 -65
- package/dist/crdt/doc/steps.js +0 -311
- package/dist/crdt/text/index/cache.d.ts +0 -16
- package/dist/crdt/text/index/cache.js +0 -175
- package/dist/crdt/text/index/claims.d.ts +0 -18
- package/dist/crdt/text/index/claims.js +0 -203
- package/dist/crdt/text/index/fold.d.ts +0 -21
- package/dist/crdt/text/index/fold.js +0 -656
- package/dist/crdt/text/index/layout.d.ts +0 -22
- package/dist/crdt/text/index/layout.js +0 -258
- package/dist/crdt/text/index/placement.d.ts +0 -47
- package/dist/crdt/text/index/placement.js +0 -738
- package/dist/crdt/text/index/records.d.ts +0 -13
- package/dist/crdt/text/index/records.js +0 -139
- package/dist/crdt/text/index/report.d.ts +0 -18
- package/dist/crdt/text/index/report.js +0 -337
- package/dist/crdt/text/index/state.d.ts +0 -197
- package/dist/crdt/text/index/state.js +0 -116
- package/dist/crdt/text/index/streams.d.ts +0 -25
- package/dist/crdt/text/index/streams.js +0 -248
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
2
|
import './richtext.css';
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
Plugin,
|
|
5
|
+
BlockDefinition,
|
|
6
|
+
BlockSnippetPayload,
|
|
7
|
+
PlaceholderView
|
|
8
|
+
} from '../../plugins.js';
|
|
4
9
|
import { branded } from '../brand.js';
|
|
5
10
|
import type { Edytor } from '../../edytor.svelte.js';
|
|
6
11
|
import { presetId } from '../../kinds.js';
|
|
@@ -191,11 +196,15 @@
|
|
|
191
196
|
let retyping: string | null = null;
|
|
192
197
|
/** The disclosures this view created, opened once drawn (`onBlockAttached`). */
|
|
193
198
|
const opening = new Set<string>();
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
+
/** Each kind's presets, their keywords the view's (`labels`) where it names some. */
|
|
200
|
+
const named = (blocks: Record<string, BlockDefinition>) => {
|
|
201
|
+
for (const [type, kind] of Object.entries(blocks))
|
|
202
|
+
kind.presets = kind.presets?.map((row, index, rows) => {
|
|
203
|
+
const own = keywordsOf(edytor, presetId(type, index, rows.length), row.keywords);
|
|
204
|
+
return own ? { ...row, keywords: own } : row;
|
|
205
|
+
});
|
|
206
|
+
return blocks;
|
|
207
|
+
};
|
|
199
208
|
const operations = richTextOperations(edytor);
|
|
200
209
|
/** The space an autolink types itself: its own insertion is not looked at again. */
|
|
201
210
|
let autolinking = false;
|
|
@@ -395,62 +404,57 @@
|
|
|
395
404
|
])
|
|
396
405
|
)
|
|
397
406
|
},
|
|
398
|
-
blocks: {
|
|
407
|
+
blocks: named({
|
|
399
408
|
paragraph: {
|
|
400
409
|
snippet: paragraph,
|
|
401
|
-
presets:
|
|
402
|
-
{ label: kinds.paragraph, icon: 'T', keywords: ['paragraph', 'plain'] }
|
|
403
|
-
])
|
|
410
|
+
presets: [{ label: kinds.paragraph, icon: 'T', keywords: ['paragraph', 'plain'] }]
|
|
404
411
|
},
|
|
405
412
|
heading: {
|
|
406
413
|
...headingRead,
|
|
407
414
|
...headingWrite,
|
|
408
415
|
snippet: textThenChildren,
|
|
409
|
-
presets:
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
markdown: [`${'#'.repeat(index + 1)} `]
|
|
417
|
-
}))
|
|
418
|
-
)
|
|
416
|
+
presets: HEADING_LEVELS.map((level, index) => ({
|
|
417
|
+
label: [kinds.heading1, kinds.heading2, kinds.heading3][index]!,
|
|
418
|
+
icon: `H${['₁', '₂', '₃'][index]}`,
|
|
419
|
+
keywords: [level, ...[['title'], ['subtitle'], []][index]!],
|
|
420
|
+
data: { level },
|
|
421
|
+
markdown: [`${'#'.repeat(index + 1)} `]
|
|
422
|
+
}))
|
|
419
423
|
},
|
|
420
424
|
'bulleted-list-item': {
|
|
421
425
|
...itemRead,
|
|
422
426
|
...itemWrite,
|
|
423
427
|
continues: true,
|
|
424
428
|
snippet: listItem,
|
|
425
|
-
presets:
|
|
429
|
+
presets: [
|
|
426
430
|
{
|
|
427
431
|
label: kinds.bulletedList,
|
|
428
432
|
icon: '•',
|
|
429
433
|
keywords: ['bullet', 'ul'],
|
|
430
434
|
markdown: ['- ', '* ', '+ ']
|
|
431
435
|
}
|
|
432
|
-
]
|
|
436
|
+
]
|
|
433
437
|
},
|
|
434
438
|
'numbered-list-item': {
|
|
435
439
|
...numberedRead,
|
|
436
440
|
...itemWrite,
|
|
437
441
|
continues: true,
|
|
438
442
|
snippet: listItem,
|
|
439
|
-
presets:
|
|
443
|
+
presets: [
|
|
440
444
|
{
|
|
441
445
|
label: kinds.numberedList,
|
|
442
446
|
icon: '1.',
|
|
443
447
|
keywords: ['number', 'ol'],
|
|
444
448
|
markdown: ['1. ', 'a. ', 'i. ']
|
|
445
449
|
}
|
|
446
|
-
]
|
|
450
|
+
]
|
|
447
451
|
},
|
|
448
452
|
'todo-item': {
|
|
449
453
|
...todoWrite,
|
|
450
454
|
continues: true,
|
|
451
455
|
// The app's control markup, if any, through its own snippet: the default costs no branch.
|
|
452
456
|
snippet: checkbox ? todoItemOwn : todoItem,
|
|
453
|
-
presets:
|
|
457
|
+
presets: [
|
|
454
458
|
{
|
|
455
459
|
label: kinds.todoList,
|
|
456
460
|
icon: '☐',
|
|
@@ -458,7 +462,7 @@
|
|
|
458
462
|
data: { checked: false },
|
|
459
463
|
markdown: ['[ ] ', '[] ']
|
|
460
464
|
}
|
|
461
|
-
]
|
|
465
|
+
]
|
|
462
466
|
},
|
|
463
467
|
toggle: {
|
|
464
468
|
...toggleRead,
|
|
@@ -466,14 +470,14 @@
|
|
|
466
470
|
container: true,
|
|
467
471
|
snippet: emptyBody ? detailsOwn : details,
|
|
468
472
|
...disclosure,
|
|
469
|
-
presets:
|
|
473
|
+
presets: [
|
|
470
474
|
{
|
|
471
475
|
label: kinds.toggleList,
|
|
472
476
|
icon: '▸',
|
|
473
477
|
keywords: ['details', 'expand'],
|
|
474
478
|
markdown: ['> ']
|
|
475
479
|
}
|
|
476
|
-
]
|
|
480
|
+
]
|
|
477
481
|
},
|
|
478
482
|
// Notion's toggle headings: a heading's text as a toggle's header over its
|
|
479
483
|
// children. From a heading, `>` + space at its start makes it one (its level
|
|
@@ -485,23 +489,20 @@
|
|
|
485
489
|
container: true,
|
|
486
490
|
snippet: emptyBody ? detailsOwn : details,
|
|
487
491
|
...disclosure,
|
|
488
|
-
presets:
|
|
489
|
-
'
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
};
|
|
503
|
-
})
|
|
504
|
-
)
|
|
492
|
+
presets: HEADING_LEVELS.map((level, index) => {
|
|
493
|
+
const hashes = '#'.repeat(index + 1);
|
|
494
|
+
return {
|
|
495
|
+
label: [kinds.toggleHeading1, kinds.toggleHeading2, kinds.toggleHeading3][index]!,
|
|
496
|
+
icon: `▸H${['₁', '₂', '₃'][index]}`,
|
|
497
|
+
keywords: ['toggle', 'heading', 'collapse'],
|
|
498
|
+
data: { level },
|
|
499
|
+
markdownFrom: {
|
|
500
|
+
heading: ['> '],
|
|
501
|
+
toggle: [`${hashes} `],
|
|
502
|
+
'toggle-heading': [`${hashes} `]
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
})
|
|
505
506
|
},
|
|
506
507
|
// Notion's callout: an icon (`data.icon`, the picker's), a title (its own
|
|
507
508
|
// text) and a content (its children, any blocks), which shows even empty
|
|
@@ -511,9 +512,7 @@
|
|
|
511
512
|
container: true,
|
|
512
513
|
body: true,
|
|
513
514
|
snippet: calloutIcon || emptyBody ? calloutOwn : callout,
|
|
514
|
-
presets:
|
|
515
|
-
{ label: kinds.callout, icon: '✦', keywords: ['note', 'tip'], data: { icon } }
|
|
516
|
-
]),
|
|
515
|
+
presets: [{ label: kinds.callout, icon: '✦', keywords: ['note', 'tip'], data: { icon } }],
|
|
517
516
|
// A callout without its own icon exports the view's.
|
|
518
517
|
...calloutWrite((data) => iconOf(data, edytor)),
|
|
519
518
|
// The block menu's Change icon, while the view lists a picker: the keyboard's way to it.
|
|
@@ -537,15 +536,15 @@
|
|
|
537
536
|
container: true,
|
|
538
537
|
snippet: textThenChildren,
|
|
539
538
|
// Notion: `"` + space is a quote; `>` + space is a toggle.
|
|
540
|
-
presets:
|
|
539
|
+
presets: [{ label: kinds.quote, icon: '❝', markdown: ['" '] }]
|
|
541
540
|
},
|
|
542
541
|
divider: {
|
|
543
542
|
...richTextKinds.divider,
|
|
544
543
|
...dividerRead,
|
|
545
544
|
...dividerWrite,
|
|
546
|
-
presets:
|
|
545
|
+
presets: [
|
|
547
546
|
{ label: kinds.divider, icon: '—', keywords: ['hr', 'separator'], markdown: ['---'] }
|
|
548
|
-
]
|
|
547
|
+
],
|
|
549
548
|
empty: { content: [], children: [] }
|
|
550
549
|
},
|
|
551
550
|
'ordered-list': {
|
|
@@ -561,7 +560,7 @@
|
|
|
561
560
|
snippet: list
|
|
562
561
|
},
|
|
563
562
|
'list-item': { ...itemRead, ...itemWrite, snippet: listItem }
|
|
564
|
-
}
|
|
563
|
+
})
|
|
565
564
|
};
|
|
566
565
|
};
|
|
567
566
|
</script>
|
|
@@ -575,10 +574,13 @@
|
|
|
575
574
|
nested snippet or a text node between tags is one more node per block for
|
|
576
575
|
the browser to walk (`render.markers`).
|
|
577
576
|
-->
|
|
577
|
+
<!-- prettier-ignore -->
|
|
578
|
+
{#snippet nested(children: Snippet)}<div data-edytor-children>{@render children()}</div>{/snippet}
|
|
579
|
+
|
|
578
580
|
<!-- prettier-ignore -->
|
|
579
581
|
{#snippet paragraph({ content, children }: BlockSnippetPayload)}<!--
|
|
580
582
|
--><p>{@render content()}</p><!--
|
|
581
|
-
-->{#if children}
|
|
583
|
+
-->{#if children}{@render nested(children)}{/if}<!--
|
|
582
584
|
-->{/snippet}
|
|
583
585
|
|
|
584
586
|
<!--
|
|
@@ -588,7 +590,7 @@
|
|
|
588
590
|
<!-- prettier-ignore -->
|
|
589
591
|
{#snippet details({ block, content, children }: BlockSnippetPayload)}<!--
|
|
590
592
|
--><summary>{@render content()}</summary><!--
|
|
591
|
-
-->{#if children}
|
|
593
|
+
-->{#if children}{@render nested(children)}<!--
|
|
592
594
|
-->{:else}<EmptyBody {block} kind="toggle" />{/if}<!--
|
|
593
595
|
-->{/snippet}
|
|
594
596
|
|
|
@@ -600,7 +602,7 @@
|
|
|
600
602
|
<!-- prettier-ignore -->
|
|
601
603
|
{#snippet textThenChildren({ content, children }: BlockSnippetPayload)}<!--
|
|
602
604
|
-->{@render content()}<!--
|
|
603
|
-
-->{#if children}
|
|
605
|
+
-->{#if children}{@render nested(children)}{/if}<!--
|
|
604
606
|
-->{/snippet}
|
|
605
607
|
|
|
606
608
|
<!--
|
|
@@ -611,7 +613,7 @@
|
|
|
611
613
|
{#snippet callout({ block, content, children }: BlockSnippetPayload<{ icon?: string }>)}<!--
|
|
612
614
|
--><CalloutIcon {block} /><!--
|
|
613
615
|
--><div data-edytor-callout-title>{@render content()}</div><!--
|
|
614
|
-
-->{#if children}
|
|
616
|
+
-->{#if children}{@render nested(children)}<!--
|
|
615
617
|
-->{:else}<EmptyBody {block} kind="callout" />{/if}<!--
|
|
616
618
|
-->{/snippet}
|
|
617
619
|
|
|
@@ -619,7 +621,7 @@
|
|
|
619
621
|
{#snippet todoItem({ block, content, children }: BlockSnippetPayload<{ checked?: boolean }>)}<!--
|
|
620
622
|
--><TodoCheckbox {block} /><!--
|
|
621
623
|
--><div>{@render content()}</div><!--
|
|
622
|
-
-->{#if children}
|
|
624
|
+
-->{#if children}{@render nested(children)}{/if}<!--
|
|
623
625
|
-->{/snippet}
|
|
624
626
|
|
|
625
627
|
<!--
|
|
@@ -630,13 +632,13 @@
|
|
|
630
632
|
{#snippet todoItemOwn({ block, content, children }: BlockSnippetPayload<{ checked?: boolean }>)}<!--
|
|
631
633
|
--><RichTextControl {block} control="checkbox" /><!--
|
|
632
634
|
--><div>{@render content()}</div><!--
|
|
633
|
-
-->{#if children}
|
|
635
|
+
-->{#if children}{@render nested(children)}{/if}<!--
|
|
634
636
|
-->{/snippet}
|
|
635
637
|
|
|
636
638
|
<!-- prettier-ignore -->
|
|
637
639
|
{#snippet detailsOwn({ block, content, children }: BlockSnippetPayload)}<!--
|
|
638
640
|
--><summary>{@render content()}</summary><!--
|
|
639
|
-
-->{#if children}
|
|
641
|
+
-->{#if children}{@render nested(children)}<!--
|
|
640
642
|
-->{:else}<RichTextControl {block} control="toggle" />{/if}<!--
|
|
641
643
|
-->{/snippet}
|
|
642
644
|
|
|
@@ -644,14 +646,14 @@
|
|
|
644
646
|
{#snippet calloutOwn({ block, content, children }: BlockSnippetPayload<{ icon?: string }>)}<!--
|
|
645
647
|
--><RichTextControl {block} control="calloutIcon" /><!--
|
|
646
648
|
--><div data-edytor-callout-title>{@render content()}</div><!--
|
|
647
|
-
-->{#if children}
|
|
649
|
+
-->{#if children}{@render nested(children)}<!--
|
|
648
650
|
-->{:else}<RichTextControl {block} control="callout" />{/if}<!--
|
|
649
651
|
-->{/snippet}
|
|
650
652
|
|
|
651
653
|
<!-- prettier-ignore -->
|
|
652
654
|
{#snippet listItem({ content, children }: BlockSnippetPayload)}<!--
|
|
653
655
|
--><div>{@render content()}</div><!--
|
|
654
|
-
-->{#if children}
|
|
656
|
+
-->{#if children}{@render nested(children)}{/if}<!--
|
|
655
657
|
-->{/snippet}
|
|
656
658
|
|
|
657
659
|
<!--
|
|
@@ -74,9 +74,11 @@ export const openLink = (href, view = globalThis.window ?? null) => {
|
|
|
74
74
|
if (safe)
|
|
75
75
|
view?.open(safe, '_blank', 'noopener,noreferrer');
|
|
76
76
|
};
|
|
77
|
+
/** A mark's value as written: `true` when none is given. */
|
|
78
|
+
const markValue = (value) => value === undefined ? true : value;
|
|
77
79
|
/** Set `mark` to `value` (`null`: remove it) over `start`–`end` of `text`: the view's `formatRange`. */
|
|
78
80
|
const markText = (edytor, text, start, end, mark, value) => edytor.formatRange(text.parent.id, text.segStart + start, end - start, {
|
|
79
|
-
[mark]: value
|
|
81
|
+
[mark]: markValue(value)
|
|
80
82
|
});
|
|
81
83
|
const formatSelectedTextRange = (edytor, mark, value, toggle) => {
|
|
82
84
|
const { yStart, yEnd, startText, endText } = edytor.selection.textRange();
|
|
@@ -87,7 +89,7 @@ const formatSelectedTextRange = (edytor, mark, value, toggle) => {
|
|
|
87
89
|
// One decision for the whole range (Notion): a toggle removes the mark only
|
|
88
90
|
// when every selected character has it, else it marks all of them.
|
|
89
91
|
const on = spans.every(({ text, start, end }) => text.getMarksAtRange(start, end).every(({ marks }) => marks && mark in marks));
|
|
90
|
-
const next = toggle && on ? null : value
|
|
92
|
+
const next = toggle && on ? null : markValue(value);
|
|
91
93
|
// One command per span: a vetoed one keeps its marks (`dispatcher.each`).
|
|
92
94
|
edytor.dispatcher.each('format', spans, ({ text, start, end }) => edytor.formatRange(text.parent.id, text.segStart + start, end - start, { [mark]: next }));
|
|
93
95
|
// Selected blocks stay selected (Notion; a divider's phantom text is no range).
|
|
@@ -99,6 +101,23 @@ const formatSelectedTextRange = (edytor, mark, value, toggle) => {
|
|
|
99
101
|
* mark at (a lone selected divider's text is a phantom).
|
|
100
102
|
*/
|
|
101
103
|
const selectsBlocks = (edytor) => edytor.selection.value.kind === 'blocks';
|
|
104
|
+
/**
|
|
105
|
+
* At a caret (no block selection: a lone divider's text is a phantom),
|
|
106
|
+
* stage what `next` makes of the full set the next insertion carries
|
|
107
|
+
* (values kept), as a `format` command, and keep the caret; `false` over a
|
|
108
|
+
* range.
|
|
109
|
+
*/
|
|
110
|
+
const stageAtCaret = (edytor, next) => {
|
|
111
|
+
const { startText, yStart } = edytor.selection.textRange();
|
|
112
|
+
const { selection } = edytor;
|
|
113
|
+
if (!selection.projection.isCollapsed)
|
|
114
|
+
return false;
|
|
115
|
+
if (startText && !selectsBlocks(edytor)) {
|
|
116
|
+
edytor.dispatcher.run('format', () => selection.stage(next(marksForInsertion(startText, yStart, { pending: selection.pending }))));
|
|
117
|
+
selection.selectText(startText, yStart);
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
};
|
|
102
121
|
export const richTextOperations = (edytor) => ({
|
|
103
122
|
/**
|
|
104
123
|
* Insert a `divider` void block at the caret — the native
|
|
@@ -210,20 +229,8 @@ export const richTextOperations = (edytor) => ({
|
|
|
210
229
|
const safeValue = mark === 'color' || mark === 'highlight' ? sanitizeCssColorValue(value) : value;
|
|
211
230
|
if (safeValue === null)
|
|
212
231
|
return;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (isCollapsed) {
|
|
216
|
-
if (startText && !selectsBlocks(edytor)) {
|
|
217
|
-
// Stage the full set the next insertion carries, values kept.
|
|
218
|
-
edytor.dispatcher.run('format', () => edytor.selection.stage({
|
|
219
|
-
...marksForInsertion(startText, yStart, { pending: edytor.selection.pending }),
|
|
220
|
-
[mark]: safeValue
|
|
221
|
-
}));
|
|
222
|
-
edytor.selection.selectText(startText, yStart);
|
|
223
|
-
}
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
formatSelectedTextRange(edytor, mark, safeValue, false);
|
|
232
|
+
if (!stageAtCaret(edytor, (marks) => ({ ...marks, [mark]: safeValue })))
|
|
233
|
+
formatSelectedTextRange(edytor, mark, safeValue, false);
|
|
227
234
|
},
|
|
228
235
|
/**
|
|
229
236
|
* Toggle `mark` (any mark the editor defines: the rich text ones are
|
|
@@ -231,21 +238,8 @@ export const richTextOperations = (edytor) => ({
|
|
|
231
238
|
* stage it for the next character typed.
|
|
232
239
|
*/
|
|
233
240
|
setMarkAtRange: (mark, value) => {
|
|
234
|
-
const {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (startText && !selectsBlocks(edytor)) {
|
|
238
|
-
// A caret stages the full set the next insertion carries (values kept).
|
|
239
|
-
edytor.dispatcher.run('format', () => {
|
|
240
|
-
const { [mark]: current = null, ...rest } = marksForInsertion(startText, yStart, {
|
|
241
|
-
pending: selection.pending
|
|
242
|
-
});
|
|
243
|
-
selection.stage(current !== null ? rest : { ...rest, [mark]: value === undefined ? true : value });
|
|
244
|
-
});
|
|
245
|
-
selection.selectText(startText, yStart, endText, yEnd);
|
|
246
|
-
}
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
formatSelectedTextRange(edytor, mark, value, true);
|
|
241
|
+
const toggled = ({ [mark]: current = null, ...rest }) => current !== null ? rest : { ...rest, [mark]: markValue(value) };
|
|
242
|
+
if (!stageAtCaret(edytor, toggled))
|
|
243
|
+
formatSelectedTextRange(edytor, mark, value, true);
|
|
250
244
|
}
|
|
251
245
|
});
|
|
@@ -34,7 +34,7 @@ export type Caret = {
|
|
|
34
34
|
* order), comparing boundary points — a focus on an ancestor element of the
|
|
35
35
|
* anchor (`(textElement, childCount)`) is after it.
|
|
36
36
|
*/
|
|
37
|
-
export declare const isBackward: (
|
|
37
|
+
export declare const isBackward: ({ anchorNode, anchorOffset, focusNode, focusOffset }: {
|
|
38
38
|
anchorNode: Node | null;
|
|
39
39
|
anchorOffset: number;
|
|
40
40
|
focusNode: Node | null;
|