edytor 4.0.0 → 6.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 +2 -2
- package/dist/block/block.svelte.js +6 -21
- package/dist/block/inlineBlock.svelte.js +4 -6
- package/dist/clipboard/moveText.js +6 -8
- package/dist/cloudflare/DocumentRoom.d.ts +10 -3
- package/dist/cloudflare/index.d.ts +1 -1
- package/dist/cloudflare/room/access.d.ts +8 -3
- package/dist/cloudflare/room/access.js +44 -20
- package/dist/cloudflare/room/admission.js +28 -5
- package/dist/cloudflare/room/comments.js +28 -1
- package/dist/cloudflare/room/context.d.ts +106 -109
- package/dist/cloudflare/room/context.js +10 -3
- package/dist/cloudflare/room/forged.d.ts +14 -0
- package/dist/cloudflare/room/forged.js +30 -2
- package/dist/cloudflare/room/frames.d.ts +4 -0
- package/dist/cloudflare/room/frames.js +5 -1
- package/dist/cloudflare/room/purge.js +2 -1
- package/dist/cloudflare/room/replicas.d.ts +4 -2
- package/dist/cloudflare/room/replicas.js +8 -3
- package/dist/cloudflare/room/suggest.d.ts +7 -0
- package/dist/cloudflare/room/suggest.js +143 -0
- package/dist/cloudflare/routeDocumentSocket.d.ts +10 -1
- package/dist/cloudflare/routeDocumentSocket.js +5 -3
- package/dist/components/Content.svelte +9 -1
- package/dist/crdt/anchors.js +3 -2
- package/dist/crdt/attribution/attribution.d.ts +3 -0
- package/dist/crdt/attribution/attribution.js +3 -3
- package/dist/crdt/doc/funnel.d.ts +1 -1
- package/dist/crdt/doc/funnel.js +5 -2
- package/dist/crdt/doc/gate.d.ts +1 -1
- package/dist/crdt/doc/history.d.ts +6 -0
- package/dist/crdt/doc/history.js +79 -59
- package/dist/crdt/doc/ops.d.ts +81 -0
- package/dist/crdt/doc/ops.js +1898 -0
- package/dist/crdt/doc/reads.d.ts +2 -2
- package/dist/crdt/doc/reads.js +18 -28
- package/dist/crdt/doc/suggest.d.ts +167 -0
- package/dist/crdt/doc/suggest.js +579 -0
- package/dist/crdt/document.d.ts +10 -1
- package/dist/crdt/document.js +19 -11
- package/dist/crdt/edytor-doc.d.ts +294 -272
- package/dist/crdt/edytor-doc.js +123 -108
- package/dist/crdt/engine-api.d.ts +8 -2
- package/dist/crdt/engine.d.ts +2 -1
- package/dist/crdt/engine.js +3 -1
- package/dist/crdt/flow.d.ts +5 -47
- package/dist/crdt/flow.js +3 -269
- package/dist/crdt/index.d.ts +102 -108
- package/dist/crdt/index.js +3 -0
- package/dist/crdt/operations.d.ts +13 -4
- package/dist/crdt/parts.d.ts +43 -0
- package/dist/crdt/parts.js +44 -0
- package/dist/crdt/placement/display.d.ts +1 -1
- package/dist/crdt/placement/display.js +3 -3
- package/dist/crdt/placement/keys.d.ts +5 -0
- package/dist/crdt/placement/keys.js +5 -0
- package/dist/crdt/placement/resolve.d.ts +1 -1
- package/dist/crdt/placement/resolve.js +2 -1
- package/dist/crdt/protocol.d.ts +1 -1
- package/dist/crdt/protocol.js +1 -1
- package/dist/crdt/protocols/auth.d.ts +11 -1
- package/dist/crdt/protocols/auth.js +17 -1
- package/dist/crdt/protocols/awareness.js +46 -81
- package/dist/crdt/protocols/comments.d.ts +16 -1
- package/dist/crdt/protocols/comments.js +9 -1
- package/dist/crdt/providers/index.d.ts +12 -22
- package/dist/crdt/providers/index.js +3 -1
- package/dist/crdt/providers/indexeddb.d.ts +6 -10
- package/dist/crdt/providers/indexeddb.js +10 -16
- package/dist/crdt/providers/room.d.ts +11 -0
- package/dist/crdt/providers/room.js +26 -12
- package/dist/crdt/providers/websocket.d.ts +8 -12
- package/dist/crdt/providers/websocket.js +17 -19
- package/dist/crdt/purge.d.ts +7 -1
- package/dist/crdt/purge.js +32 -2
- package/dist/crdt/rangeDelete.d.ts +2 -47
- package/dist/crdt/rangeDelete.js +1 -169
- package/dist/crdt/restore.d.ts +2 -1
- package/dist/crdt/restore.js +9 -0
- package/dist/crdt/schema.d.ts +9 -1
- package/dist/crdt/schema.js +13 -3
- package/dist/crdt/structs.d.ts +2 -0
- package/dist/crdt/structs.js +2 -0
- package/dist/crdt/text/index/build.d.ts +125 -0
- package/dist/crdt/text/index/build.js +2895 -0
- package/dist/crdt/text/index/shared.d.ts +2 -6
- package/dist/crdt/text/index/shared.js +19 -19
- package/dist/crdt/text/model.d.ts +10 -0
- package/dist/crdt/text/model.js +10 -4
- package/dist/crdt/text/runs.d.ts +43 -42
- package/dist/crdt/text/runs.js +2 -165
- package/dist/crdt/vendor/yjs/UPSTREAM.md +83 -0
- package/dist/crdt/vendor/yjs/dts/index.d.ts +1 -1
- package/dist/crdt/vendor/yjs/dts/structs/Item.d.ts +6 -3
- package/dist/crdt/vendor/yjs/dts/ynode.d.ts +13 -2
- package/dist/crdt/vendor/yjs/src/index.js +1 -1
- package/dist/crdt/vendor/yjs/src/structs/Item.js +44 -5
- package/dist/crdt/vendor/yjs/src/ynode.js +29 -7
- package/dist/edytor.svelte.d.ts +1 -1
- package/dist/edytor.svelte.js +100 -75
- package/dist/events/beforeInputCommands.d.ts +2 -2
- package/dist/events/domTextOffset.d.ts +2 -0
- package/dist/events/domTextOffset.js +8 -0
- package/dist/events/nativeInteractiveControl.d.ts +11 -1
- package/dist/events/nativeInteractiveControl.js +41 -1
- package/dist/events/onBeforeInput.js +3 -3
- package/dist/events/onFocus.js +9 -11
- package/dist/export/forms.d.ts +75 -75
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -1
- package/dist/labels.d.ts +8 -0
- package/dist/labels.js +6 -1
- package/dist/plugins/comments/CommentsController.svelte.d.ts +5 -1
- package/dist/plugins/comments/CommentsController.svelte.js +14 -4
- package/dist/plugins/find/search.js +2 -2
- package/dist/plugins/index.d.ts +1 -0
- package/dist/plugins/index.js +1 -0
- package/dist/plugins/richtext/RichTextPlugin.svelte +63 -61
- package/dist/plugins/richtext/richTextOperations.js +26 -32
- package/dist/plugins/suggestions/SuggestionBars.svelte +40 -23
- package/dist/plugins/suggestions/SuggestionBars.svelte.d.ts +3 -1
- package/dist/plugins/suggestions/SuggestionRuns.svelte +77 -0
- package/dist/plugins/suggestions/SuggestionRuns.svelte.d.ts +9 -0
- package/dist/plugins/suggestions/shared.svelte.d.ts +58 -0
- package/dist/plugins/suggestions/shared.svelte.js +421 -0
- package/dist/plugins/suggestions/suggestionsPlugin.d.ts +15 -4
- package/dist/selection/domSelection.d.ts +2 -0
- package/dist/selection/domSelection.js +6 -2
- package/dist/selection/pointer.svelte.js +75 -48
- package/dist/selection/replaceSelection.js +2 -6
- package/dist/selection/selection.svelte.d.ts +3 -3
- package/dist/selection/selection.svelte.js +39 -32
- package/dist/selection/selection.utils.d.ts +14 -0
- package/dist/selection/selection.utils.js +33 -11
- package/dist/session/commands.js +21 -11
- package/dist/session/composition.svelte.d.ts +5 -3
- package/dist/session/composition.svelte.js +21 -17
- package/dist/session/handles.d.ts +4 -17
- package/dist/session/handles.js +14 -44
- package/dist/session/navigation.js +6 -9
- package/dist/session/selection.d.ts +11 -5
- package/dist/session/selection.js +14 -48
- package/dist/session/suggestions.svelte.d.ts +38 -9
- package/dist/session/suggestions.svelte.js +37 -18
- package/dist/session/virtual.d.ts +1 -1
- package/dist/surface/attributes.d.ts +5 -0
- package/dist/surface/attributes.js +2 -2
- package/dist/surface/cells.d.ts +2 -26
- package/dist/surface/cells.js +2 -33
- package/dist/surface/observer.svelte.d.ts +6 -1
- package/dist/surface/observer.svelte.js +143 -111
- package/dist/surface/pin.svelte.d.ts +9 -9
- package/dist/surface/pin.svelte.js +15 -22
- package/dist/surface/projector.svelte.js +10 -16
- package/dist/text/text.svelte.js +14 -19
- package/dist/themes/notion.css +29 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +7 -0
- package/package.json +3 -1
- package/dist/crdt/doc/capability.d.ts +0 -56
- package/dist/crdt/doc/capability.js +0 -211
- 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 -199
- 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 -225
- 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 -24
- package/dist/crdt/text/index/layout.js +0 -271
- package/dist/crdt/text/index/placement.d.ts +0 -47
- package/dist/crdt/text/index/placement.js +0 -741
- 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
|
@@ -39,11 +39,12 @@
|
|
|
39
39
|
* from the observer's records (`rendered`, `sel.drift.unreported`).
|
|
40
40
|
*/
|
|
41
41
|
import { untrack } from 'svelte';
|
|
42
|
-
import { clearDomSelection, createDomRange, getActiveElement, getDomSelection, getDomSelectionSnapshot, scrollCaretIntoView } from '../selection/domSelection.js';
|
|
42
|
+
import { clearDomSelection, createDomRange, focusIn, getActiveElement, getDomSelection, getDomSelectionSnapshot, scrollCaretIntoView } from '../selection/domSelection.js';
|
|
43
43
|
import { getMarkEdgeSide, getYIndex, isTextBoundSelectionPoint } from '../selection/selection.utils.js';
|
|
44
44
|
import { project, sameEnds } from '../session/selection.js';
|
|
45
45
|
import { isAndroidChromeBrowser } from '../events/events.utils.js';
|
|
46
46
|
import { isKindControl, isOwnPoint } from '../events/nativeInteractiveControl.js';
|
|
47
|
+
import { textLeaves } from '../events/domTextOffset.js';
|
|
47
48
|
/**
|
|
48
49
|
* Named rule: Android Chrome mutates the DOM after a
|
|
49
50
|
* canceled `deleteContentBackward` and reports the caret one position right
|
|
@@ -54,9 +55,8 @@ const ANDROID_SNAP_BACK_MS = 250;
|
|
|
54
55
|
const IME_POST_COMMIT_JUMP_MS = 100;
|
|
55
56
|
/** The DOM point of a text-segment offset: its text leaf, else the element itself. */
|
|
56
57
|
export const domPointOf = (element, offset) => {
|
|
57
|
-
const walker = element.ownerDocument.createTreeWalker(element, NodeFilter.SHOW_TEXT);
|
|
58
58
|
let at = 0;
|
|
59
|
-
for (
|
|
59
|
+
for (const leaf of textLeaves(element)) {
|
|
60
60
|
const end = at + leaf.length;
|
|
61
61
|
if (offset <= end)
|
|
62
62
|
return [leaf, offset - at];
|
|
@@ -116,9 +116,7 @@ export class Projector {
|
|
|
116
116
|
pre = () => {
|
|
117
117
|
void this.#deps();
|
|
118
118
|
untrack(() => {
|
|
119
|
-
|
|
120
|
-
const active = node && getActiveElement(node);
|
|
121
|
-
this.#noted = active && node.contains(active) ? active : null;
|
|
119
|
+
this.#noted = focusIn(this.edytor.node);
|
|
122
120
|
});
|
|
123
121
|
};
|
|
124
122
|
/** Root post-flush `$effect`: every DOM write of the flush has landed. */
|
|
@@ -430,8 +428,7 @@ export class Projector {
|
|
|
430
428
|
// whose `selectionchange` can land after that key counted as a
|
|
431
429
|
// gesture) leaves any caret the browser parks later its own
|
|
432
430
|
// (`#parking`, `placed`), whatever key comes before it.
|
|
433
|
-
|
|
434
|
-
if (requested && value.kind !== 'blocks' && active && node.contains(active))
|
|
431
|
+
if (requested && value.kind !== 'blocks' && focusIn(node))
|
|
435
432
|
this.#parking = true;
|
|
436
433
|
const inside = !!dom?.anchorNode && node.contains(dom.anchorNode);
|
|
437
434
|
const clears = value.kind === 'none' ? requested && inside : inside || dom?.rangeCount === 0;
|
|
@@ -459,15 +456,11 @@ export class Projector {
|
|
|
459
456
|
if (!this.#shows(dom, range, isReversed)) {
|
|
460
457
|
if (!this.#ours(requested))
|
|
461
458
|
return true;
|
|
462
|
-
|
|
463
|
-
if (!(active && node.contains(active))) {
|
|
459
|
+
if (!focusIn(node)) {
|
|
464
460
|
// Focus first (focusing a host can move the selection): the editing
|
|
465
461
|
// host, never a focusable element inside the text such as a link.
|
|
466
|
-
edytor.expectInternalFocus();
|
|
467
462
|
const element = start[0] instanceof HTMLElement ? start[0] : start[0].parentElement;
|
|
468
|
-
(element?.closest('[contenteditable="true"]') ?? node)
|
|
469
|
-
preventScroll: true
|
|
470
|
-
});
|
|
463
|
+
edytor.expectInternalFocus(element?.closest('[contenteditable="true"]') ?? node);
|
|
471
464
|
}
|
|
472
465
|
this.#write(getDomSelection(node) ?? dom, points, isCollapsed);
|
|
473
466
|
if (selection.scrollOnDisplay)
|
|
@@ -551,10 +544,11 @@ export class Projector {
|
|
|
551
544
|
#ours = (requested) => {
|
|
552
545
|
const node = this.edytor.node;
|
|
553
546
|
const active = getActiveElement(node);
|
|
547
|
+
const inside = focusIn(node);
|
|
554
548
|
// A form control or a foreign editable in a kind's chrome (a field bound to
|
|
555
549
|
// `block.data`) owns its focus and its selection: never written over.
|
|
556
|
-
if (
|
|
557
|
-
return !isKindControl(node,
|
|
550
|
+
if (inside)
|
|
551
|
+
return !isKindControl(node, inside);
|
|
558
552
|
const document = node.ownerDocument;
|
|
559
553
|
if (active && active !== document.body && active !== document.documentElement)
|
|
560
554
|
return false;
|
package/dist/text/text.svelte.js
CHANGED
|
@@ -27,28 +27,26 @@ export class Text {
|
|
|
27
27
|
get parent() {
|
|
28
28
|
return this.edytor.idToBlock.block(this.blockId);
|
|
29
29
|
}
|
|
30
|
-
/** This segment in its block's parts
|
|
31
|
-
get #
|
|
30
|
+
/** This segment in its block's parts. */
|
|
31
|
+
get #parts() {
|
|
32
32
|
if ($effect.tracking())
|
|
33
33
|
this.edytor.cells?.get(this.blockId);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return { part, index };
|
|
39
|
-
}
|
|
40
|
-
return { index: -1 };
|
|
34
|
+
return this.edytor.idToBlock.parts(this.blockId);
|
|
35
|
+
}
|
|
36
|
+
get #part() {
|
|
37
|
+
return this.#parts.find((part) => part.kind === 'text' && part.segOrd === this.ordinal);
|
|
41
38
|
}
|
|
42
39
|
/** Position among the block's content parts. */
|
|
43
40
|
get index() {
|
|
44
|
-
|
|
41
|
+
const parts = this.#parts;
|
|
42
|
+
return parts.indexOf(this.#part);
|
|
45
43
|
}
|
|
46
44
|
get value() {
|
|
47
|
-
return segmentJSON(this.#
|
|
45
|
+
return segmentJSON(this.#part?.runs ?? []);
|
|
48
46
|
}
|
|
49
47
|
/** The segment's text. */
|
|
50
48
|
get stringContent() {
|
|
51
|
-
return this.#
|
|
49
|
+
return this.#part?.text ?? '';
|
|
52
50
|
}
|
|
53
51
|
get isEmpty() {
|
|
54
52
|
return this.length === 0;
|
|
@@ -57,15 +55,15 @@ export class Text {
|
|
|
57
55
|
return this.stringContent.endsWith('\n');
|
|
58
56
|
}
|
|
59
57
|
get length() {
|
|
60
|
-
return this
|
|
58
|
+
return this.stringContent.length;
|
|
61
59
|
}
|
|
62
60
|
/** Display offset of this segment inside its block's content (atoms count 1). */
|
|
63
61
|
get segStart() {
|
|
64
|
-
return this.#
|
|
62
|
+
return this.#part?.start ?? 0;
|
|
65
63
|
}
|
|
66
64
|
/** The segment is shown by a live block. */
|
|
67
65
|
get isInDocument() {
|
|
68
|
-
return this.parent.isInTree && this.#
|
|
66
|
+
return this.parent.isInTree && this.#part !== undefined;
|
|
69
67
|
}
|
|
70
68
|
/**
|
|
71
69
|
* Re-create this block's text elements from its cell — the repair of a
|
|
@@ -101,9 +99,8 @@ export class Text {
|
|
|
101
99
|
/** @internal */
|
|
102
100
|
attach = (node) => {
|
|
103
101
|
this.node = node;
|
|
104
|
-
this.edytor.nodeToText.set(node, this);
|
|
105
102
|
// Registered, it takes the attribute table's attributes (`surface/attributes.ts`).
|
|
106
|
-
const release = this.edytor.surface.register(node, 'text', this.parent.id);
|
|
103
|
+
const release = this.edytor.surface.register(node, 'text', this.parent.id, undefined, undefined, this);
|
|
107
104
|
const pluginDestroy = cleanups(this.edytor.plugins, (plugin) => plugin.onTextAttached?.({ node, text: this }));
|
|
108
105
|
// A display that waited for a mounted destination runs in the next pass.
|
|
109
106
|
this.edytor.projector?.mounted(this);
|
|
@@ -111,8 +108,6 @@ export class Text {
|
|
|
111
108
|
destroy: () => {
|
|
112
109
|
if (this.node === node)
|
|
113
110
|
this.node = undefined;
|
|
114
|
-
if (this.edytor.nodeToText.get(node) === this)
|
|
115
|
-
this.edytor.nodeToText.delete(node);
|
|
116
111
|
release();
|
|
117
112
|
pluginDestroy.forEach((destroy) => destroy());
|
|
118
113
|
}
|
package/dist/themes/notion.css
CHANGED
|
@@ -807,6 +807,35 @@
|
|
|
807
807
|
color: var(--notion-placeholder);
|
|
808
808
|
}
|
|
809
809
|
|
|
810
|
+
/*
|
|
811
|
+
* Shared suggestions: a block a suggestion made or proposes to delete
|
|
812
|
+
* (`data-edytor-suggested`), and its runs in the overlay, in its author's
|
|
813
|
+
* colour (`--edytor-range-color`).
|
|
814
|
+
*/
|
|
815
|
+
.edytor-notion [data-edytor-suggested='inserted'],
|
|
816
|
+
.edytor-notion [data-edytor-suggested='split'] {
|
|
817
|
+
box-shadow: inset 2px 0 0 var(--edytor-suggestion-inserted, rgb(68 131 97));
|
|
818
|
+
background: var(--edytor-suggestion-inserted-background, rgba(68, 131, 97, 0.07));
|
|
819
|
+
}
|
|
820
|
+
.edytor-notion [data-edytor-suggested='removed'] {
|
|
821
|
+
background: var(--edytor-suggestion-removed);
|
|
822
|
+
}
|
|
823
|
+
.edytor-notion [data-edytor-suggested='removed'] [data-edytor-text] {
|
|
824
|
+
text-decoration: line-through;
|
|
825
|
+
text-decoration-color: var(--edytor-suggestion-removed-line, rgba(235, 87, 87, 0.6));
|
|
826
|
+
}
|
|
827
|
+
:is(.edytor-notion, .edytor-notion + [data-edytor-overlay]) [data-edytor-suggestion-inserted] {
|
|
828
|
+
border-bottom: 2px solid var(--edytor-range-color, rgb(68 131 97));
|
|
829
|
+
background: color-mix(in srgb, var(--edytor-range-color, rgb(68 131 97)) 10%, transparent);
|
|
830
|
+
}
|
|
831
|
+
:is(.edytor-notion, .edytor-notion + [data-edytor-overlay]) [data-edytor-suggestion-deleted] {
|
|
832
|
+
background: linear-gradient(
|
|
833
|
+
transparent calc(50% - 1px),
|
|
834
|
+
var(--edytor-range-color, rgb(235 87 87)) calc(50% - 1px) calc(50% + 1px),
|
|
835
|
+
transparent calc(50% + 1px)
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
|
|
810
839
|
/* ── Page (a subpage link) and table of contents ────────────────────── */
|
|
811
840
|
.edytor-notion [data-edytor-type='page'] {
|
|
812
841
|
margin: 1px 0;
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export declare const id: (prefix: "t" | "b" | "i" | "v" | "s" | "c") => string;
|
|
2
|
+
/** `key`'s value in `map`, made (`make`) and stored when it holds none. */
|
|
3
|
+
export declare const getOr: <K, V>(map: {
|
|
4
|
+
get(key: K): V | undefined;
|
|
5
|
+
set(key: K, value: V): unknown;
|
|
6
|
+
}, key: K, make: () => V) => V;
|
|
2
7
|
/**
|
|
3
8
|
* A hook's veto as the dispatcher carries it: what `prevent(cb?)` recorded,
|
|
4
9
|
* and the extension that recorded it. Hooks never see one thrown at them:
|
package/dist/utils.js
CHANGED
|
@@ -6,6 +6,13 @@ export const id = (prefix) => {
|
|
|
6
6
|
t += alphabet[61 & r[n]];
|
|
7
7
|
return `${prefix}_${t}`;
|
|
8
8
|
};
|
|
9
|
+
/** `key`'s value in `map`, made (`make`) and stored when it holds none. */
|
|
10
|
+
export const getOr = (map, key, make) => {
|
|
11
|
+
let value = map.get(key);
|
|
12
|
+
if (value === undefined)
|
|
13
|
+
map.set(key, (value = make()));
|
|
14
|
+
return value;
|
|
15
|
+
};
|
|
9
16
|
/**
|
|
10
17
|
* A hook's veto as the dispatcher carries it: what `prevent(cb?)` recorded,
|
|
11
18
|
* and the extension that recorded it. Hooks never see one thrown at them:
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "edytor",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "6.0.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/beynar/edytor.git"
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"check:deps": "node scripts/deps.mjs",
|
|
24
24
|
"check:docs": "node scripts/check-docs.mjs",
|
|
25
25
|
"check:api": "node scripts/api-report.mjs --check",
|
|
26
|
+
"check:size": "node scripts/bundle-size.mjs --budget bundle-budget.json --top 0",
|
|
26
27
|
"api:report": "node scripts/api-report.mjs",
|
|
27
28
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
28
29
|
"lint": "prettier --check . && eslint .",
|
|
@@ -50,6 +51,7 @@
|
|
|
50
51
|
"bench:browser": "node bench/browser.js",
|
|
51
52
|
"bench:runs": "BENCH_RUNS=1 vitest --config ./vitest.crdt.config.ts --run src/tests/crdt/runs/runs.bench.test.ts",
|
|
52
53
|
"size": "node scripts/bundle-size.mjs --by dir",
|
|
54
|
+
"bundle:attribution": "node scripts/bundle-attribution.mjs",
|
|
53
55
|
"registry:build": "shadcn-svelte registry build ./registry.json --output site/public/r",
|
|
54
56
|
"check:registry": "node scripts/check-registry.mjs",
|
|
55
57
|
"test:registry-install": "sh tests/registry-install/run.sh",
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Structural capability: one answer in advance and at execution.
|
|
3
|
-
*
|
|
4
|
-
* ── Island/void enforcement (operation-layer, not stored) ──────────────
|
|
5
|
-
*
|
|
6
|
-
* Roles are resolved operation-time via `config.roleOf(type)`, through one
|
|
7
|
-
* role table (`DisplayRoles`) that the index's display and every guard
|
|
8
|
-
* below ask of a block's shown kind — the editor derives them from plugin
|
|
9
|
-
* block definitions, exactly like the baseline's
|
|
10
|
-
* `block.definition.island/void`. They are deliberately NOT replicated block
|
|
11
|
-
* data: the schema stays policy-free, and a replica with different plugins
|
|
12
|
-
* reads the same document. Enforced rules (baseline semantics, see
|
|
13
|
-
* `plugins.ts` + `block.utils.ts`):
|
|
14
|
-
*
|
|
15
|
-
* - `void`: cannot accept children (insert/move/nest into void rejected),
|
|
16
|
-
* cannot merge either direction, cannot be split. Content edits ARE
|
|
17
|
-
* allowed — void rendered content (captions) stays editable per the
|
|
18
|
-
* baseline contract. Deletion is allowed (delete is not editing).
|
|
19
|
-
* - `island`: editable content, but its subtree is structurally sealed —
|
|
20
|
-
* a block inside an island (`insideIsland`) cannot be moved, nested,
|
|
21
|
-
* unnested, or merged across the island boundary. Merges INSIDE one
|
|
22
|
-
* island are allowed; merging an island child into the island itself is
|
|
23
|
-
* allowed when the island renders its content. Moving or merging
|
|
24
|
-
* INTO an island subtree is rejected. An island declared `lines` (code)
|
|
25
|
-
* holds only lines of its `defaultChild` kind, and a line holds no
|
|
26
|
-
* children (the display enforces it against undo,
|
|
27
|
-
* `insertBlocks` refuses a line parent); any other island keeps its
|
|
28
|
-
* structure. `canPlace` and `canMerge` are the one answer, asked in
|
|
29
|
-
* advance or by the ops.
|
|
30
|
-
* - Island merge: when an island block itself is merged (backward or
|
|
31
|
-
* forward), its children are unnested to the vacated sibling slot and
|
|
32
|
-
* reset to the default child of that slot's parent (`defaultChild`).
|
|
33
|
-
* - Baseline merges NEVER adopt the merged block's children — they unnest
|
|
34
|
-
* to the vacated slot. The facade exposes both: `mergeBlocks` is the
|
|
35
|
-
* engine primitive (children adopt into the target
|
|
36
|
-
* by staying under the source, which the target's claim displays),
|
|
37
|
-
* `mergeBackward`/`mergeForward` reproduce the baseline command shape.
|
|
38
|
-
*
|
|
39
|
-
*/
|
|
40
|
-
import { type BlockId } from '../placement/model.js';
|
|
41
|
-
import type { DocBase, DocReads } from './reads.js';
|
|
42
|
-
/** The capability of the facade over its reads. */
|
|
43
|
-
export declare const docCapability: (c: DocBase & DocReads) => {
|
|
44
|
-
canPlace: (ids: readonly BlockId[], parent?: BlockId | null, kindOf?: (id: BlockId) => string | undefined) => boolean;
|
|
45
|
-
isContainer: (id: BlockId) => boolean;
|
|
46
|
-
fits: (parent: BlockId | null, kind: string | undefined) => boolean;
|
|
47
|
-
fitsIn: (parentType: string, kind: string | undefined) => boolean;
|
|
48
|
-
fitted: (parent: BlockId | null, kind: string | undefined) => string | undefined;
|
|
49
|
-
emptiable: (id: BlockId) => boolean;
|
|
50
|
-
nestParent: (ids: readonly BlockId[], parent: BlockId) => BlockId;
|
|
51
|
-
canMerge: (fromId: BlockId, intoId: BlockId) => boolean;
|
|
52
|
-
rendersContent: (id: BlockId) => boolean;
|
|
53
|
-
defaultChild: (parent: BlockId | null) => string;
|
|
54
|
-
kindToCopy: (id: BlockId) => string;
|
|
55
|
-
};
|
|
56
|
-
export type DocCapability = ReturnType<typeof docCapability>;
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Structural capability: one answer in advance and at execution.
|
|
3
|
-
*
|
|
4
|
-
* ── Island/void enforcement (operation-layer, not stored) ──────────────
|
|
5
|
-
*
|
|
6
|
-
* Roles are resolved operation-time via `config.roleOf(type)`, through one
|
|
7
|
-
* role table (`DisplayRoles`) that the index's display and every guard
|
|
8
|
-
* below ask of a block's shown kind — the editor derives them from plugin
|
|
9
|
-
* block definitions, exactly like the baseline's
|
|
10
|
-
* `block.definition.island/void`. They are deliberately NOT replicated block
|
|
11
|
-
* data: the schema stays policy-free, and a replica with different plugins
|
|
12
|
-
* reads the same document. Enforced rules (baseline semantics, see
|
|
13
|
-
* `plugins.ts` + `block.utils.ts`):
|
|
14
|
-
*
|
|
15
|
-
* - `void`: cannot accept children (insert/move/nest into void rejected),
|
|
16
|
-
* cannot merge either direction, cannot be split. Content edits ARE
|
|
17
|
-
* allowed — void rendered content (captions) stays editable per the
|
|
18
|
-
* baseline contract. Deletion is allowed (delete is not editing).
|
|
19
|
-
* - `island`: editable content, but its subtree is structurally sealed —
|
|
20
|
-
* a block inside an island (`insideIsland`) cannot be moved, nested,
|
|
21
|
-
* unnested, or merged across the island boundary. Merges INSIDE one
|
|
22
|
-
* island are allowed; merging an island child into the island itself is
|
|
23
|
-
* allowed when the island renders its content. Moving or merging
|
|
24
|
-
* INTO an island subtree is rejected. An island declared `lines` (code)
|
|
25
|
-
* holds only lines of its `defaultChild` kind, and a line holds no
|
|
26
|
-
* children (the display enforces it against undo,
|
|
27
|
-
* `insertBlocks` refuses a line parent); any other island keeps its
|
|
28
|
-
* structure. `canPlace` and `canMerge` are the one answer, asked in
|
|
29
|
-
* advance or by the ops.
|
|
30
|
-
* - Island merge: when an island block itself is merged (backward or
|
|
31
|
-
* forward), its children are unnested to the vacated sibling slot and
|
|
32
|
-
* reset to the default child of that slot's parent (`defaultChild`).
|
|
33
|
-
* - Baseline merges NEVER adopt the merged block's children — they unnest
|
|
34
|
-
* to the vacated slot. The facade exposes both: `mergeBlocks` is the
|
|
35
|
-
* engine primitive (children adopt into the target
|
|
36
|
-
* by staying under the source, which the target's claim displays),
|
|
37
|
-
* `mergeBackward`/`mergeForward` reproduce the baseline command shape.
|
|
38
|
-
*
|
|
39
|
-
*/
|
|
40
|
-
import { isLiveIn } from '../placement/model.js';
|
|
41
|
-
/** The capability of the facade over its reads. */
|
|
42
|
-
export const docCapability = (c) => {
|
|
43
|
-
const { roles, rendersContentOf, view, blockTypeOf, childrenIds, positionOf, ancestorsOf, isVoid, isIsland, islandOf, insideIsland, holdsLayout, insideItem, isRowKind, isCellKind, displayLength } = c;
|
|
44
|
-
// ── structural capability: one answer in advance and at execution ──
|
|
45
|
-
/**
|
|
46
|
-
* May `ids` be placed under `parent` (`null` = the root)? Every id is
|
|
47
|
-
* live, distinct and outside any island interior (island subtrees are
|
|
48
|
-
* sealed); the destination is live, neither void nor an island nor
|
|
49
|
-
* inside one, and not inside any moved block's own subtree; and every
|
|
50
|
-
* block fits it as the kind `kindOf` gives (`fits`; a move keeps its
|
|
51
|
-
* kind) or already sits in it (a reorder changes nothing a list holds:
|
|
52
|
-
* an image shed into a list still moves among its items); and
|
|
53
|
-
* no layout, nor a block holding one, lands inside a layout item
|
|
54
|
-
* (`layout.nest`). A table's cell never moves, and its row only
|
|
55
|
-
* within its table (`table.fits`).
|
|
56
|
-
* Without a `parent`: may these blocks move at all (the drag
|
|
57
|
-
* affordance). The move ops refuse exactly when this answers `false`.
|
|
58
|
-
* (`insertBlock` is looser — island interiors are built by inserting
|
|
59
|
-
* into them.)
|
|
60
|
-
*/
|
|
61
|
-
const canPlace = (ids, parent, kindOf = blockTypeOf) => {
|
|
62
|
-
const v = view();
|
|
63
|
-
if (ids.length === 0 || new Set(ids).size !== ids.length)
|
|
64
|
-
return false;
|
|
65
|
-
if (ids.some((id) => !isLiveIn(v, id) || insideIsland(id, v)))
|
|
66
|
-
return false;
|
|
67
|
-
// A cell moves only with its column, never as a block (`table.fits`).
|
|
68
|
-
if (ids.some((id) => isCellKind(blockTypeOf(id))))
|
|
69
|
-
return false;
|
|
70
|
-
// A row moves only within its table: a table's rows are reordered, never re-homed.
|
|
71
|
-
const rows = ids.filter((id) => isRowKind(blockTypeOf(id)));
|
|
72
|
-
if (rows.length > 0 && parent !== undefined)
|
|
73
|
-
if (parent === null || rows.some((id) => positionOf(id)?.parent !== parent))
|
|
74
|
-
return false;
|
|
75
|
-
if (parent === undefined || parent === null)
|
|
76
|
-
return true;
|
|
77
|
-
if (!isLiveIn(v, parent) || isVoid(parent))
|
|
78
|
-
return false;
|
|
79
|
-
const stays = (id) => positionOf(id)?.parent === parent;
|
|
80
|
-
if (!ids.every((id) => stays(id) || fits(parent, kindOf(id))))
|
|
81
|
-
return false;
|
|
82
|
-
if (insideItem(parent, v) && ids.some(holdsLayout))
|
|
83
|
-
return false;
|
|
84
|
-
return ![parent, ...ancestorsOf(parent, v)].some((a) => isIsland(a) || ids.includes(a));
|
|
85
|
-
};
|
|
86
|
-
/**
|
|
87
|
-
* A container: a block that shows only its children — no content of
|
|
88
|
-
* its own, neither void nor an island (a list, a table row, a column).
|
|
89
|
-
*/
|
|
90
|
-
const isContainer = (id) => !rendersContent(id) && !isVoid(id) && !isIsland(id);
|
|
91
|
-
/**
|
|
92
|
-
* THE container rule: may a block of `kind` sit directly
|
|
93
|
-
* under `parent` (`null` = the root)? A container whose default child is
|
|
94
|
-
* a kind of its own — its item (a list's `list-item`, a columns
|
|
95
|
-
* layout's `column`) — holds only its items, and containers of them
|
|
96
|
-
* when the item renders content (a list directly in a list, from JSON
|
|
97
|
-
* or the API; a layout holds no layout, `layout.fits`); one whose
|
|
98
|
-
* default child is the document's (a column) holds any block. Every
|
|
99
|
-
* structural placement asks it: a move is refused where its blocks do
|
|
100
|
-
* not fit (`canPlace`), Tab nests under a container's last item
|
|
101
|
-
* (`nestParent`), an outdent or a lift is refused where the block would
|
|
102
|
-
* not fit, and a block a merge, delete or range sheds into a container
|
|
103
|
-
* takes its item kind when it is a plain block (`fitted`). Explicit kind
|
|
104
|
-
* writes (`insertBlocks`, a retype) place what they are told (the
|
|
105
|
-
* view's Turn into places the kind where it fits first, `liftOut`); a
|
|
106
|
-
* plain block stored directly in a list still shows as its item (the
|
|
107
|
-
* index's `typeOf`), whatever write or race put it there.
|
|
108
|
-
*/
|
|
109
|
-
const fits = (parent, kind) => parent === null || fitsIn(blockTypeOf(parent) ?? '', kind);
|
|
110
|
-
/** `fits`, by the parent's kind (a block not written yet: `placeBeside`'s new item). */
|
|
111
|
-
const fitsIn = (parentType, kind) => {
|
|
112
|
-
if (!roles.container(parentType))
|
|
113
|
-
return true;
|
|
114
|
-
const item = roles.defaultChild(parentType);
|
|
115
|
-
if (item === defaultChild(null) || kind === item)
|
|
116
|
-
return true;
|
|
117
|
-
return (kind !== undefined &&
|
|
118
|
-
roles.container(kind) &&
|
|
119
|
-
roles.defaultChild(kind) === item &&
|
|
120
|
-
rendersContentOf(item));
|
|
121
|
-
};
|
|
122
|
-
/**
|
|
123
|
-
* `kind`, or — where a plain block (the document's default kind, or
|
|
124
|
-
* none: a pasted run) does not fit `parent` — `parent`'s item, when
|
|
125
|
-
* that renders content (a paragraph landing in a list is its item; one
|
|
126
|
-
* in a columns layout keeps its kind: its text never vanishes). Any
|
|
127
|
-
* other kind keeps its kind and data wherever a merge, a delete or a
|
|
128
|
-
* paste sheds it — an image under a bullet stays an image, a heading a
|
|
129
|
-
* heading, a to-do keeps its check — as a peer's
|
|
130
|
-
* concurrent promotion shows it (`typeOf` resets only the default
|
|
131
|
-
* kind); an outdent or a move that would place one directly in a list
|
|
132
|
-
* is refused (`fits`).
|
|
133
|
-
*/
|
|
134
|
-
const fitted = (parent, kind) => {
|
|
135
|
-
if (fits(parent, kind) || (kind !== undefined && kind !== defaultChild(null)))
|
|
136
|
-
return kind;
|
|
137
|
-
const item = defaultChild(parent);
|
|
138
|
-
return rendersContentOf(item) ? item : kind;
|
|
139
|
-
};
|
|
140
|
-
/** A container that goes once it loses every child: it holds no text of its own (a peer's retype can leave some, hidden: it renders none). */
|
|
141
|
-
const emptiable = (id) => isContainer(id) && displayLength(id) === 0;
|
|
142
|
-
/**
|
|
143
|
-
* Where `ids` nest when nested into `parent` (Tab, a drop inside it):
|
|
144
|
-
* `parent`, or — a container they are no items of — its last child,
|
|
145
|
-
* and so on down. Tab after a list nests under its last item (Notion);
|
|
146
|
-
* a last child that holds no children (an image, a code block) is
|
|
147
|
-
* answered as it is, and `canPlace` refuses it, as Tab right under
|
|
148
|
-
* that block is refused.
|
|
149
|
-
*/
|
|
150
|
-
const nestParent = (ids, parent) => {
|
|
151
|
-
let at = parent;
|
|
152
|
-
while (!ids.every((id) => fits(at, blockTypeOf(id)))) {
|
|
153
|
-
const last = childrenIds(at).at(-1);
|
|
154
|
-
if (last === undefined)
|
|
155
|
-
break;
|
|
156
|
-
at = last;
|
|
157
|
-
}
|
|
158
|
-
return at;
|
|
159
|
-
};
|
|
160
|
-
/**
|
|
161
|
-
* May `fromId`'s content merge into `intoId`? Both live and distinct,
|
|
162
|
-
* neither void, `intoId` renders its content (a list, a table row or a
|
|
163
|
-
* code block shows none, so a first item, cell or line never merges
|
|
164
|
-
* into it), `fromId` renders its own unless it is
|
|
165
|
-
* an island (a list or a row never merges as a whole: its items would
|
|
166
|
-
* leave it, `del.merge.container`), and the merge stays on one side of an island
|
|
167
|
-
* boundary (a block may merge into its own island root — that stays
|
|
168
|
-
* inside — but nothing from outside merges into an island).
|
|
169
|
-
*/
|
|
170
|
-
const canMerge = (fromId, intoId) => {
|
|
171
|
-
const v = view();
|
|
172
|
-
if (fromId === intoId || !isLiveIn(v, fromId) || !isLiveIn(v, intoId))
|
|
173
|
-
return false;
|
|
174
|
-
if (isVoid(fromId) || isVoid(intoId))
|
|
175
|
-
return false;
|
|
176
|
-
// Nothing merges into or out of a table's cell (`table.merge`).
|
|
177
|
-
if (isCellKind(blockTypeOf(fromId)) || isCellKind(blockTypeOf(intoId)))
|
|
178
|
-
return false;
|
|
179
|
-
if (!rendersContent(intoId) || !(rendersContent(fromId) || isIsland(fromId)))
|
|
180
|
-
return false;
|
|
181
|
-
const islandFrom = islandOf(fromId, v);
|
|
182
|
-
if (intoId === islandFrom)
|
|
183
|
-
return true;
|
|
184
|
-
return islandFrom === islandOf(intoId, v) && !isIsland(intoId);
|
|
185
|
-
};
|
|
186
|
-
/** `id`'s shown kind renders its content. */
|
|
187
|
-
const rendersContent = (id) => rendersContentOf(blockTypeOf(id) ?? '');
|
|
188
|
-
/** The adopted default child type under `parent` (`null` = the root). */
|
|
189
|
-
const defaultChild = (parent) => roles.defaultChild(parent === null ? null : (blockTypeOf(parent) ?? null));
|
|
190
|
-
/**
|
|
191
|
-
* The kind a new block copies from `id` (a split tail, a flow's tail,
|
|
192
|
-
* a duplicate and each of its descendants). A type a peer's retype is
|
|
193
|
-
* replacing can be missing while its new value is pending: the copy
|
|
194
|
-
* then takes its parent's default child, never a missing type
|
|
195
|
-
* (it showed as `unknown` everywhere, for good).
|
|
196
|
-
*/
|
|
197
|
-
const kindToCopy = (id) => blockTypeOf(id) ?? defaultChild(positionOf(id)?.parent ?? null);
|
|
198
|
-
return {
|
|
199
|
-
canPlace,
|
|
200
|
-
isContainer,
|
|
201
|
-
fits,
|
|
202
|
-
fitsIn,
|
|
203
|
-
fitted,
|
|
204
|
-
emptiable,
|
|
205
|
-
nestParent,
|
|
206
|
-
canMerge,
|
|
207
|
-
rendersContent,
|
|
208
|
-
defaultChild,
|
|
209
|
-
kindToCopy
|
|
210
|
-
};
|
|
211
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { BlockId, InlineSpec } from '../placement/model.js';
|
|
2
|
-
import type { Prepared } from './types.js';
|
|
3
|
-
import type { OpsContext } from './steps.js';
|
|
4
|
-
/** An inline atom to insert: its id is minted when it names none. */
|
|
5
|
-
export type NewInline = Omit<InlineSpec, 'id'> & {
|
|
6
|
-
id?: string;
|
|
7
|
-
};
|
|
8
|
-
/** The content ops of one facade, prepared. */
|
|
9
|
-
export declare const contentOps: (c: OpsContext) => {
|
|
10
|
-
insertText: (id: BlockId, offset: number, text: string, marks?: Record<string, unknown>) => Prepared;
|
|
11
|
-
deleteText: (id: BlockId, offset: number, length: number) => Prepared;
|
|
12
|
-
formatRange: (id: BlockId, offset: number, length: number, marks: Record<string, unknown>) => Prepared;
|
|
13
|
-
clearMarks: (id: BlockId, offset: number, length: number) => Prepared;
|
|
14
|
-
insertInline: (id: BlockId, offset: number, atom: NewInline) => Prepared;
|
|
15
|
-
removeInline: (id: BlockId, inlineId: string) => Prepared;
|
|
16
|
-
};
|
package/dist/crdt/doc/content.js
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Content ops: text and inline atoms in a block's display (allowed
|
|
3
|
-
* inside voids — the caption contract), and marks over a range.
|
|
4
|
-
*/
|
|
5
|
-
import { sanitizeWireJson, jsonEquals } from '../../utils/json.js';
|
|
6
|
-
import { id as newId } from '../../utils.js';
|
|
7
|
-
import { ref, sanitizeInline } from './plan.js';
|
|
8
|
-
/** The content ops of one facade, prepared. */
|
|
9
|
-
export const contentOps = (c) => {
|
|
10
|
-
const { contentTarget, REFUSED, plan, clamp, atomOf, textIn } = c;
|
|
11
|
-
// content ops (allowed inside voids — caption contract)
|
|
12
|
-
const insertText = (id, offset, text, marks) => {
|
|
13
|
-
id = ref(id);
|
|
14
|
-
const clean = ref(text);
|
|
15
|
-
if (!contentTarget(id))
|
|
16
|
-
return REFUSED;
|
|
17
|
-
const [at] = clamp(id, offset, 0);
|
|
18
|
-
const m = marks && sanitizeWireJson(marks);
|
|
19
|
-
return plan([id], clean === '' ? [] : [{ op: 'insertText', id, offset: at, text: clean, marks: m }]);
|
|
20
|
-
};
|
|
21
|
-
const deleteText = (id, offset, length) => {
|
|
22
|
-
id = ref(id);
|
|
23
|
-
if (!contentTarget(id))
|
|
24
|
-
return REFUSED;
|
|
25
|
-
const [at, end] = clamp(id, offset, length);
|
|
26
|
-
return plan([id], end > at ? [{ op: 'deleteText', id, offset: at, length: end - at }] : []);
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* Multi-mark format over a range (values may be null = unset); planned
|
|
30
|
-
* only when some text atom in the range carries a different value.
|
|
31
|
-
*/
|
|
32
|
-
const formatRange = (id, offset, length, marks) => {
|
|
33
|
-
id = ref(id);
|
|
34
|
-
const clean = sanitizeWireJson(marks);
|
|
35
|
-
if (!contentTarget(id))
|
|
36
|
-
return REFUSED;
|
|
37
|
-
const [at, end] = clamp(id, offset, length);
|
|
38
|
-
// Planned only when some text atom carries another value (the same-value guard).
|
|
39
|
-
const differs = textIn(id, at, end).some((item) => Object.keys(clean).some((k) => !jsonEquals(item.marks?.[k] ?? null, clean[k] ?? null)));
|
|
40
|
-
return plan([id], differs ? [{ op: 'formatRange', id, offset: at, length: end - at, marks: clean }] : []);
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* Baseline `removeMarksFromText`: every mark present anywhere in the
|
|
44
|
-
* range is unset over the range. Names are discovered from the content.
|
|
45
|
-
*/
|
|
46
|
-
const clearMarks = (id, offset, length) => {
|
|
47
|
-
const clears = {};
|
|
48
|
-
for (const item of textIn(ref(id), offset, offset + length)) {
|
|
49
|
-
for (const k of Object.keys(item.marks ?? {}))
|
|
50
|
-
clears[k] = null;
|
|
51
|
-
}
|
|
52
|
-
return formatRange(id, offset, length, clears);
|
|
53
|
-
};
|
|
54
|
-
/** An atom without an id gets a fresh one. */
|
|
55
|
-
const insertInline = (id, offset, atom) => {
|
|
56
|
-
id = ref(id);
|
|
57
|
-
const clean = sanitizeInline({ ...atom, id: atom.id ?? newId('i') });
|
|
58
|
-
if (!contentTarget(id))
|
|
59
|
-
return REFUSED;
|
|
60
|
-
return plan([id], [{ op: 'insertInline', id, offset: clamp(id, offset, 0)[0], atom: clean }]);
|
|
61
|
-
};
|
|
62
|
-
const removeInline = (id, inlineId) => {
|
|
63
|
-
id = ref(id);
|
|
64
|
-
const atom = atomOf(id, ref(inlineId));
|
|
65
|
-
if (atom === undefined)
|
|
66
|
-
return REFUSED;
|
|
67
|
-
return plan([id], [{ op: 'removeInline', id, offset: atom.at, inlineId: ref(inlineId) }]);
|
|
68
|
-
};
|
|
69
|
-
return {
|
|
70
|
-
insertText,
|
|
71
|
-
deleteText,
|
|
72
|
-
formatRange,
|
|
73
|
-
clearMarks,
|
|
74
|
-
insertInline,
|
|
75
|
-
removeInline
|
|
76
|
-
};
|
|
77
|
-
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Block deletion (`del.blocks.promote`): only the members leave, the
|
|
3
|
-
* unselected children of a deleted block take its slot.
|
|
4
|
-
*/
|
|
5
|
-
import type { BlockId } from '../placement/model.js';
|
|
6
|
-
import type { Prepared } from './types.js';
|
|
7
|
-
import type { OpsContext } from './steps.js';
|
|
8
|
-
/** The delete ops of one facade, prepared. */
|
|
9
|
-
export declare const deleteOps: (c: OpsContext) => {
|
|
10
|
-
deleteBlocks: (ids: readonly BlockId[], subtree?: boolean, filled?: BlockId | null) => Prepared;
|
|
11
|
-
deleteBlock: (id: BlockId, opts?: {
|
|
12
|
-
keepChildren?: boolean;
|
|
13
|
-
}) => Prepared;
|
|
14
|
-
};
|
|
15
|
-
export type DeleteOps = ReturnType<typeof deleteOps>;
|