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
|
@@ -15,28 +15,15 @@
|
|
|
15
15
|
import { Block } from '../block/block.svelte.js';
|
|
16
16
|
import { InlineBlock } from '../block/inlineBlock.svelte.js';
|
|
17
17
|
import { Text } from '../text/text.svelte.js';
|
|
18
|
-
import type {
|
|
18
|
+
import type { Destination, DocChange } from '../crdt/index.js';
|
|
19
|
+
import { type Part } from '../crdt/parts.js';
|
|
19
20
|
import type { Edytor } from '../edytor.svelte.js';
|
|
20
21
|
import type { JSONText } from '../utils/json.js';
|
|
21
22
|
export type TextRunItem = {
|
|
22
23
|
text: string;
|
|
23
24
|
marks?: Record<string, unknown>;
|
|
24
25
|
};
|
|
25
|
-
|
|
26
|
-
export type ContentPart = {
|
|
27
|
-
kind: 'text';
|
|
28
|
-
start: number;
|
|
29
|
-
items: TextRunItem[];
|
|
30
|
-
length: number;
|
|
31
|
-
} | {
|
|
32
|
-
kind: 'inline';
|
|
33
|
-
start: number;
|
|
34
|
-
item: ContentItem & {
|
|
35
|
-
kind: 'inline';
|
|
36
|
-
};
|
|
37
|
-
};
|
|
38
|
-
/** Segments and atoms, alternating, starting and ending with a segment. */
|
|
39
|
-
export declare const contentParts: (items: readonly ContentItem[]) => ContentPart[];
|
|
26
|
+
export type { Part as ContentPart } from '../crdt/parts.js';
|
|
40
27
|
/**
|
|
41
28
|
* A text segment's items as `JSONText` runs (`text.value`): empty items
|
|
42
29
|
* dropped, adjacent runs with the same marks joined, each run's marks a
|
|
@@ -65,7 +52,7 @@ export declare class Handles {
|
|
|
65
52
|
* Block `id`'s content parts — from its runs, which the index keeps per
|
|
66
53
|
* block and reads-your-writes (a write elsewhere re-derives nothing here).
|
|
67
54
|
*/
|
|
68
|
-
parts: (id: string) =>
|
|
55
|
+
parts: (id: string) => readonly Part[];
|
|
69
56
|
/** Forget the handles of the blocks a commit removed. */
|
|
70
57
|
prune: (change: DocChange) => void;
|
|
71
58
|
clear: () => void;
|
package/dist/session/handles.js
CHANGED
|
@@ -15,29 +15,8 @@
|
|
|
15
15
|
import { Block } from '../block/block.svelte.js';
|
|
16
16
|
import { InlineBlock } from '../block/inlineBlock.svelte.js';
|
|
17
17
|
import { Text } from '../text/text.svelte.js';
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const parts = [];
|
|
21
|
-
let segment = [];
|
|
22
|
-
let start = 0;
|
|
23
|
-
let at = 0;
|
|
24
|
-
const close = () => {
|
|
25
|
-
parts.push({ kind: 'text', start, items: segment, length: at - start });
|
|
26
|
-
segment = [];
|
|
27
|
-
};
|
|
28
|
-
for (const item of items) {
|
|
29
|
-
if (item.kind === 'text') {
|
|
30
|
-
segment.push({ text: item.text, ...(item.marks ? { marks: item.marks } : {}) });
|
|
31
|
-
at += item.text.length;
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
close();
|
|
35
|
-
parts.push({ kind: 'inline', start: at, item });
|
|
36
|
-
start = ++at;
|
|
37
|
-
}
|
|
38
|
-
close();
|
|
39
|
-
return parts;
|
|
40
|
-
};
|
|
18
|
+
import { partsOf } from '../crdt/parts.js';
|
|
19
|
+
import { getOr } from '../utils.js';
|
|
41
20
|
/**
|
|
42
21
|
* A text segment's items as `JSONText` runs (`text.value`): empty items
|
|
43
22
|
* dropped, adjacent runs with the same marks joined, each run's marks a
|
|
@@ -58,7 +37,7 @@ export const segmentJSON = (items) => {
|
|
|
58
37
|
}
|
|
59
38
|
return out;
|
|
60
39
|
};
|
|
61
|
-
const ROOT =
|
|
40
|
+
const ROOT = partsOf([]);
|
|
62
41
|
/** The destination right after `block` among its siblings (`after: false`, right before it). */
|
|
63
42
|
export const siblingSlot = (block, after = true) => {
|
|
64
43
|
const parent = block.parent;
|
|
@@ -73,7 +52,7 @@ export class Handles {
|
|
|
73
52
|
#blocks = new Map();
|
|
74
53
|
#texts = new Map();
|
|
75
54
|
#atoms = new Map();
|
|
76
|
-
/** Each block's parts
|
|
55
|
+
/** Each block's parts as last read (a dead block's: what it last showed). */
|
|
77
56
|
#parts = new Map();
|
|
78
57
|
constructor(edytor) {
|
|
79
58
|
this.edytor = edytor;
|
|
@@ -83,10 +62,7 @@ export class Handles {
|
|
|
83
62
|
block = (id) => {
|
|
84
63
|
if (id === 'root')
|
|
85
64
|
return this.root;
|
|
86
|
-
|
|
87
|
-
if (!block)
|
|
88
|
-
this.#blocks.set(id, (block = new Block(this.edytor, id)));
|
|
89
|
-
return block;
|
|
65
|
+
return getOr(this.#blocks, id, () => new Block(this.edytor, id));
|
|
90
66
|
};
|
|
91
67
|
/** The handle of live block `id`; none for a dead or unknown id. */
|
|
92
68
|
get = (id) => id === 'root' || (id && this.edytor.facade.isVisibleBlock(id)) ? this.block(id) : undefined;
|
|
@@ -106,9 +82,7 @@ export class Handles {
|
|
|
106
82
|
};
|
|
107
83
|
/** The handle of atom `atomId`, shown in block `blockId`. */
|
|
108
84
|
atom = (blockId, atomId) => {
|
|
109
|
-
|
|
110
|
-
if (!atom)
|
|
111
|
-
this.#atoms.set(atomId, (atom = new InlineBlock(this.edytor, blockId, atomId)));
|
|
85
|
+
const atom = getOr(this.#atoms, atomId, () => new InlineBlock(this.edytor, blockId, atomId));
|
|
112
86
|
atom.blockId = blockId;
|
|
113
87
|
return atom;
|
|
114
88
|
};
|
|
@@ -119,24 +93,20 @@ export class Handles {
|
|
|
119
93
|
parts = (id) => {
|
|
120
94
|
if (id === 'root')
|
|
121
95
|
return ROOT;
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
const runs = facade.runs(id);
|
|
125
|
-
if (memo?.runs === runs)
|
|
126
|
-
return memo.parts;
|
|
96
|
+
const parts = partsOf(this.edytor.facade.runs(id));
|
|
97
|
+
const last = this.#parts.get(id);
|
|
127
98
|
// A dead block keeps what it last showed (a write's intent is minted from it).
|
|
128
|
-
if (
|
|
129
|
-
return
|
|
130
|
-
|
|
131
|
-
this.#parts.set(id, { runs, parts });
|
|
99
|
+
if (last && last !== parts && !this.edytor.facade.isVisibleBlock(id))
|
|
100
|
+
return last;
|
|
101
|
+
this.#parts.set(id, parts);
|
|
132
102
|
return parts;
|
|
133
103
|
};
|
|
134
104
|
/** Forget the handles of the blocks a commit removed. */
|
|
135
105
|
prune = (change) => {
|
|
136
106
|
for (const id of change.removed) {
|
|
137
|
-
for (const part of this.#parts.get(id)
|
|
138
|
-
if (part.kind === 'inline' && this.#atoms.get(part.
|
|
139
|
-
this.#atoms.delete(part.
|
|
107
|
+
for (const part of this.#parts.get(id) ?? [])
|
|
108
|
+
if (part.kind === 'inline' && this.#atoms.get(part.id)?.blockId === id)
|
|
109
|
+
this.#atoms.delete(part.id);
|
|
140
110
|
this.#blocks.delete(id);
|
|
141
111
|
this.#texts.delete(id);
|
|
142
112
|
this.#parts.delete(id);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getNextGraphemeEnd, getNextWordEndOffset, getPreviousGraphemeStart, getPreviousWordStartOffset } from '../text/wordBoundary.js';
|
|
2
2
|
import { getSelectedBlocksInDocumentOrder, shownText } from '../selection/replaceSelection.js';
|
|
3
|
-
import { atomSelection, segmentsOf } from './selection.js';
|
|
3
|
+
import { atomSelection, pointOf, segmentsOf } from './selection.js';
|
|
4
4
|
import { untargeted } from './attempt.js';
|
|
5
5
|
import { siblingSlot } from './handles.js';
|
|
6
|
+
import { partsOf } from '../crdt/parts.js';
|
|
6
7
|
/**
|
|
7
8
|
* With no target (`untargeted`: the value is `none`, no gesture placed the
|
|
8
9
|
* host's caret), a navigation key does nothing (Notion): it is claimed, so
|
|
@@ -16,9 +17,8 @@ export const stayWithoutTarget = (edytor, prevent) => {
|
|
|
16
17
|
/** An inline atom in a block's string. */
|
|
17
18
|
const ATOM = '';
|
|
18
19
|
/** A block's content as one string, each inline atom one `ATOM`. */
|
|
19
|
-
const stringOf = (edytor, block) => edytor.facade
|
|
20
|
-
.
|
|
21
|
-
.map((item) => (item.kind === 'text' ? item.text : ATOM))
|
|
20
|
+
const stringOf = (edytor, block) => partsOf(edytor.facade.runs(block))
|
|
21
|
+
.map((part) => (part.kind === 'text' ? part.text : ATOM))
|
|
22
22
|
.join('');
|
|
23
23
|
/** A block's stop at its edge in `dir` (its end going forward, its start going backward). */
|
|
24
24
|
const edge = (edytor, block, dir) => ({
|
|
@@ -68,9 +68,7 @@ const select = (edytor, anchor, focus) => {
|
|
|
68
68
|
: null;
|
|
69
69
|
if (atom?.kind === 'inline')
|
|
70
70
|
return selection.select(atomSelection(start.block, atom.id, order > 0 ? 'after' : 'before'));
|
|
71
|
-
|
|
72
|
-
if (from && to)
|
|
73
|
-
selection.selectText(from.text, from.offset, to.text, to.offset, order > 0);
|
|
71
|
+
selection.selectAt(start, end, order > 0);
|
|
74
72
|
};
|
|
75
73
|
/**
|
|
76
74
|
* One apply step for `unit` in `dir` (`left`/`right`: visual). Answers whether
|
|
@@ -94,8 +92,7 @@ export const move = (edytor, unit, key, extend) => {
|
|
|
94
92
|
const first = blocks.map((block) => shownText(block, 'first')).find(Boolean);
|
|
95
93
|
const last = blocks.map((block) => shownText(block, 'last')).findLast(Boolean);
|
|
96
94
|
if (first && last) {
|
|
97
|
-
start =
|
|
98
|
-
end = { block: last.blockId, offset: last.segStart + last.length };
|
|
95
|
+
[start, end] = [pointOf(first, 0), pointOf(last, last.length)];
|
|
99
96
|
}
|
|
100
97
|
// Voids alone show no line: the blocks stay selected, except for the
|
|
101
98
|
// document keys, whose target is the document's edge (`select` moves
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
* The view's `commit(value, cause)` (public: `select(value)`) is the only writer; the
|
|
14
14
|
* presence payload is `serialize(value)`.
|
|
15
15
|
*/
|
|
16
|
-
import type { BlockId,
|
|
16
|
+
import type { BlockId, ContentRun } from '../crdt/index.js';
|
|
17
17
|
import type { DocAnchor, EdytorDoc } from '../crdt/edytor-doc.js';
|
|
18
|
+
import type { Text } from '../text/text.svelte.js';
|
|
18
19
|
export type Marks = Record<string, unknown>;
|
|
19
20
|
export type SelectionValue = {
|
|
20
21
|
readonly kind: 'none';
|
|
@@ -45,7 +46,9 @@ export type SelectionPoint = {
|
|
|
45
46
|
block: BlockId;
|
|
46
47
|
offset: number;
|
|
47
48
|
};
|
|
48
|
-
|
|
49
|
+
/** A point inside a text segment (its block's `ordinal`-th) as a block-offset point. */
|
|
50
|
+
export declare const pointOf: (text: Text, offset: number) => SelectionPoint;
|
|
51
|
+
type TextRun = Extract<ContentRun, {
|
|
49
52
|
kind: 'text';
|
|
50
53
|
}>;
|
|
51
54
|
/** One content part of a block: a text segment (between atoms, possibly empty) or an inline atom. */
|
|
@@ -55,7 +58,7 @@ export type SelectionSegment = {
|
|
|
55
58
|
segOrd: number;
|
|
56
59
|
start: number;
|
|
57
60
|
end: number;
|
|
58
|
-
runs: TextRun[];
|
|
61
|
+
runs: readonly TextRun[];
|
|
59
62
|
} | {
|
|
60
63
|
block: BlockId;
|
|
61
64
|
kind: 'inline';
|
|
@@ -88,7 +91,7 @@ export type SelectionProjection = {
|
|
|
88
91
|
readonly content: string;
|
|
89
92
|
};
|
|
90
93
|
/** The document reads a projection needs (the facade satisfies it). */
|
|
91
|
-
export type ProjectionDoc = Pick<EdytorDoc, 'version' | 'resolveAnchor' | 'compare' | 'order' | '
|
|
94
|
+
export type ProjectionDoc = Pick<EdytorDoc, 'version' | 'resolveAnchor' | 'compare' | 'order' | 'runs' | 'ancestorsOf' | 'isIsland' | 'isVoid' | 'isVisibleBlock'>;
|
|
92
95
|
/** A block's content parts; always text first and last, one text segment between two atoms. */
|
|
93
96
|
export declare const segmentsOf: (doc: ProjectionDoc, block: BlockId) => SelectionSegment[];
|
|
94
97
|
/** The projection of `value` at the document's current version, memoized per (value, version). */
|
|
@@ -97,7 +100,10 @@ export declare const project: (value: SelectionValue, doc: ProjectionDoc) => Sel
|
|
|
97
100
|
export declare const sameEnds: (a: SelectionProjection, b: SelectionProjection) => boolean;
|
|
98
101
|
/** Why a selection was written. */
|
|
99
102
|
export type SelectCause = 'dom' | 'model' | 'repair' | 'history';
|
|
100
|
-
/**
|
|
103
|
+
/**
|
|
104
|
+
* Value equality: kind, anchors (item + side), ids, pending marks. A value
|
|
105
|
+
* is plain JSON (anchors, ids, marks), so it is the JSON equality.
|
|
106
|
+
*/
|
|
101
107
|
export declare const sameValue: (a: SelectionValue, b: SelectionValue) => boolean;
|
|
102
108
|
/**
|
|
103
109
|
* The presence payload: anchors only. A text range publishes its
|
|
@@ -1,36 +1,16 @@
|
|
|
1
|
+
import { partsOf, segmentAt } from '../crdt/parts.js';
|
|
1
2
|
import { jsonEquals } from '../utils/json.js';
|
|
2
3
|
export const noSelection = Object.freeze({ kind: 'none' });
|
|
3
4
|
export const textSelection = (anchor, focus = anchor, pending) => Object.freeze({ kind: 'text', anchor, focus, ...(pending ? { pending } : {}) });
|
|
4
5
|
export const atomSelection = (blockId, atomId, from = 'before') => Object.freeze({ kind: 'atom', blockId, atomId, from });
|
|
5
6
|
export const blockSelection = (ids) => Object.freeze({ kind: 'blocks', ids: Object.freeze([...ids]) });
|
|
7
|
+
/** A point inside a text segment (its block's `ordinal`-th) as a block-offset point. */
|
|
8
|
+
export const pointOf = (text, offset) => ({
|
|
9
|
+
block: text.blockId,
|
|
10
|
+
offset: text.segStart + offset
|
|
11
|
+
});
|
|
6
12
|
/** A block's content parts; always text first and last, one text segment between two atoms. */
|
|
7
|
-
export const segmentsOf = (doc, block) => {
|
|
8
|
-
const out = [];
|
|
9
|
-
let text = { block, kind: 'text', segOrd: 0, start: 0, end: 0, runs: [] };
|
|
10
|
-
for (const item of doc.contentItems(block)) {
|
|
11
|
-
if (item.kind === 'text') {
|
|
12
|
-
text.runs.push(item);
|
|
13
|
-
text.end += item.text.length;
|
|
14
|
-
continue;
|
|
15
|
-
}
|
|
16
|
-
out.push(text, { block, kind: 'inline', id: item.id, start: text.end, end: text.end + 1 });
|
|
17
|
-
text = {
|
|
18
|
-
block,
|
|
19
|
-
kind: 'text',
|
|
20
|
-
segOrd: text.segOrd + 1,
|
|
21
|
-
start: text.end + 1,
|
|
22
|
-
end: text.end + 1,
|
|
23
|
-
runs: []
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
out.push(text);
|
|
27
|
-
return out;
|
|
28
|
-
};
|
|
29
|
-
/** The text segment displaying `offset`: the first one ending at or after it (a seam belongs to the left text). */
|
|
30
|
-
const textSegmentAt = (segments, offset) => {
|
|
31
|
-
const texts = segments.filter((s) => s.kind === 'text');
|
|
32
|
-
return texts.find((s) => offset <= s.end) ?? texts[texts.length - 1];
|
|
33
|
-
};
|
|
13
|
+
export const segmentsOf = (doc, block) => partsOf(doc.runs(block)).map((part) => ({ ...part, block }));
|
|
34
14
|
const EMPTY = {
|
|
35
15
|
start: null,
|
|
36
16
|
end: null,
|
|
@@ -95,8 +75,8 @@ const projectRange = (doc, kind, anchor, focus, atomic) => {
|
|
|
95
75
|
const partsOf = new Map(blocks.map((id) => [id, segmentsOf(doc, id)]));
|
|
96
76
|
const startSegments = partsOf.get(start.block) ?? segmentsOf(doc, start.block);
|
|
97
77
|
const endSegments = partsOf.get(end.block) ?? segmentsOf(doc, end.block);
|
|
98
|
-
const first =
|
|
99
|
-
const last =
|
|
78
|
+
const first = segmentAt(startSegments, start.offset);
|
|
79
|
+
const last = segmentAt(endSegments, end.offset);
|
|
100
80
|
const all = blocks.flatMap((id) => partsOf.get(id));
|
|
101
81
|
const from = all.indexOf(first);
|
|
102
82
|
const to = all.indexOf(last);
|
|
@@ -191,25 +171,11 @@ export const sameEnds = (a, b) => [a.start, a.end].every((p, i) => {
|
|
|
191
171
|
const q = i ? b.end : b.start;
|
|
192
172
|
return p?.block === q?.block && p?.offset === q?.offset;
|
|
193
173
|
});
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
/** Value equality: kind, anchors (item + side), ids, pending marks. */
|
|
200
|
-
export const sameValue = (a, b) => {
|
|
201
|
-
if (a === b)
|
|
202
|
-
return true;
|
|
203
|
-
if (a.kind === 'text' && b.kind === 'text')
|
|
204
|
-
return (sameAnchor(a.anchor, b.anchor) &&
|
|
205
|
-
sameAnchor(a.focus, b.focus) &&
|
|
206
|
-
sameMarks(a.pending, b.pending));
|
|
207
|
-
if (a.kind === 'blocks' && b.kind === 'blocks')
|
|
208
|
-
return a.ids.length === b.ids.length && a.ids.every((id, i) => id === b.ids[i]);
|
|
209
|
-
if (a.kind === 'atom' && b.kind === 'atom')
|
|
210
|
-
return a.blockId === b.blockId && a.atomId === b.atomId && a.from === b.from;
|
|
211
|
-
return a.kind === b.kind && a.kind === 'none';
|
|
212
|
-
};
|
|
174
|
+
/**
|
|
175
|
+
* Value equality: kind, anchors (item + side), ids, pending marks. A value
|
|
176
|
+
* is plain JSON (anchors, ids, marks), so it is the JSON equality.
|
|
177
|
+
*/
|
|
178
|
+
export const sameValue = jsonEquals;
|
|
213
179
|
/**
|
|
214
180
|
* A text value's anchors as (start, end). A range an edit collapsed has no
|
|
215
181
|
* order left; its anchors keep the roles they were minted with (a start
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BlockId, DocAnchor, OpResult } from '../crdt/index.js';
|
|
2
|
+
import type { SuggestedChange } from '../crdt/doc/suggest.js';
|
|
2
3
|
import type { Edytor } from '../edytor.svelte.js';
|
|
3
4
|
import { type JSONBlock } from '../utils/json.js';
|
|
4
5
|
/** A text range, by its anchors (a text selection's). */
|
|
@@ -48,12 +49,15 @@ export type SuggestionsAt = {
|
|
|
48
49
|
};
|
|
49
50
|
/** The block's `data-edytor-suggestion-replaced` (`empty`: folded, the preview stands in its place). */
|
|
50
51
|
export declare const replacedMark: ({ replaced, inPlace }: SuggestionsAt) => "" | "empty" | null;
|
|
52
|
+
/** A view-local suggestion: shown in this view only, until it is accepted. */
|
|
51
53
|
export declare class Suggestion {
|
|
52
54
|
#private;
|
|
53
55
|
readonly id: string;
|
|
54
56
|
/** The position, resolved: `replace: 'selection'` is the selection's blocks or text range. */
|
|
55
57
|
readonly at: ResolvedAt;
|
|
56
58
|
readonly label: string | undefined;
|
|
59
|
+
/** View-local: a {@link SharedSuggestion} is in the document. */
|
|
60
|
+
readonly shared: false;
|
|
57
61
|
constructor(layer: Suggestions, at: ResolvedAt, kind: string, options: SuggestionOptions, status: SuggestionStatus);
|
|
58
62
|
/** The proposed blocks (reactive). */
|
|
59
63
|
get content(): readonly JSONBlock[];
|
|
@@ -71,25 +75,50 @@ export declare class Suggestion {
|
|
|
71
75
|
append: (text: string) => void;
|
|
72
76
|
/** The stream ended: `status` is `ready`. */
|
|
73
77
|
done: () => void;
|
|
74
|
-
/**
|
|
78
|
+
/** Accept it: one command, one undo step, placing it in the document (see {@link Suggestions}). */
|
|
75
79
|
accept: () => OpResult;
|
|
76
|
-
/** Drop it
|
|
80
|
+
/** Drop it (writes nothing). */
|
|
77
81
|
discard: () => void;
|
|
78
82
|
/** Empty it, stream again, and ask the app for another answer (`onRetry`). */
|
|
79
83
|
retry: () => void;
|
|
80
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* A shared suggestion (`createSharedSuggestionsPlugin`): in the document,
|
|
87
|
+
* so every collaborator sees it with its author and reviews it. Its
|
|
88
|
+
* `accept()` and `reject()` are one command each (`acceptSuggestion`,
|
|
89
|
+
* `rejectSuggestion`) of a view that may decide; `discard()` rejects it
|
|
90
|
+
* (its author's withdrawal too).
|
|
91
|
+
*/
|
|
92
|
+
export interface SharedSuggestion extends Omit<Suggestion, 'shared' | 'at'> {
|
|
93
|
+
readonly shared: true;
|
|
94
|
+
/** Its position as this view added it; `null` for one another view made (its `changes` say where it is). */
|
|
95
|
+
readonly at: ResolvedAt | null;
|
|
96
|
+
/** Its author (an actor id). */
|
|
97
|
+
readonly by: string | undefined;
|
|
98
|
+
/** Who wrote it for its author (`'ai'`): a reviewer sees "Ada (AI)". */
|
|
99
|
+
readonly via: string | undefined;
|
|
100
|
+
/** Its pending changes, in document order (reactive). */
|
|
101
|
+
readonly changes: readonly SuggestedChange[];
|
|
102
|
+
/** This view may accept or reject it. */
|
|
103
|
+
readonly canDecide: boolean;
|
|
104
|
+
/** Its author is this view's actor (it may reject it). */
|
|
105
|
+
readonly mine: boolean;
|
|
106
|
+
/** Reject its changes: one command (`rejectSuggestion`). */
|
|
107
|
+
reject(): OpResult;
|
|
108
|
+
}
|
|
109
|
+
/** A suggestion of the list: view-local, or shared (with the shared suggestions plugin). */
|
|
110
|
+
export type AnySuggestion = Suggestion | SharedSuggestion;
|
|
81
111
|
/** The view's suggestions (`edytor.suggestions`). */
|
|
82
112
|
export declare class Suggestions {
|
|
83
113
|
#private;
|
|
84
|
-
|
|
85
|
-
/** Bumped by every change of a suggestion: the surface's passes re-run. */
|
|
114
|
+
readonly edytor: Edytor;
|
|
115
|
+
/** Bumped by every change of a suggestion (and of the document's): the surface's passes re-run. */
|
|
86
116
|
revision: number;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
/** The newest one: what the keys act on. */
|
|
117
|
+
/** Every suggestion, view-local and shared, oldest first (reactive). */
|
|
118
|
+
get list(): readonly AnySuggestion[];
|
|
119
|
+
/** The newest view-local one: what the keys act on. */
|
|
91
120
|
get latest(): Suggestion | undefined;
|
|
92
|
-
get: (id: string) =>
|
|
121
|
+
get: (id: string) => AnySuggestion | undefined;
|
|
93
122
|
/**
|
|
94
123
|
* Propose `content` at `at`. Throws when the position names no shown
|
|
95
124
|
* block, a block that holds no children (`inside` a void or a code line),
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Suggestions (site `editor/suggestions`): content
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
2
|
+
* Suggestions (site `editor/suggestions`): content proposed at a position —
|
|
3
|
+
* AI output, a completion, a collaborator's edit to review — in one list,
|
|
4
|
+
* each one view-local or shared (`shared`).
|
|
5
|
+
*
|
|
6
|
+
* - **View-local** (the default): shown only in this view until accepted.
|
|
7
|
+
* Session state: nothing reaches the document, its history, presence,
|
|
8
|
+
* `onChange` or another view before `accept()`, one command
|
|
9
|
+
* (`acceptSuggestion`: admission, hooks, one transaction, one undo step)
|
|
10
|
+
* placing the content as a paste places it (`flow.place`, `flow.slot`,
|
|
11
|
+
* `flow.inline`). Anchored by block ids (a text range by its anchors): it
|
|
12
|
+
* survives selection changes and edits elsewhere, and is dropped when its
|
|
13
|
+
* block dies.
|
|
14
|
+
* - **Shared** (with the shared suggestions plugin,
|
|
15
|
+
* `plugins/suggestions/shared.svelte.ts`, `sugg.*`): in the document, so
|
|
16
|
+
* every replica sees it and its author is known. The plugin adds them and
|
|
17
|
+
* lists them here (`shared`, the port it sets).
|
|
18
|
+
*
|
|
19
|
+
* The layer owns the list; the surface renders it.
|
|
11
20
|
*/
|
|
12
21
|
import { DEV } from 'esm-env';
|
|
13
22
|
import { REFUSED } from '../crdt/doc/plan.js';
|
|
@@ -34,11 +43,14 @@ const sideOf = (at) => {
|
|
|
34
43
|
const side = SIDES.find((side) => side in at);
|
|
35
44
|
return side && [side, at[side]];
|
|
36
45
|
};
|
|
46
|
+
/** A view-local suggestion: shown in this view only, until it is accepted. */
|
|
37
47
|
export class Suggestion {
|
|
38
48
|
id;
|
|
39
49
|
/** The position, resolved: `replace: 'selection'` is the selection's blocks or text range. */
|
|
40
50
|
at;
|
|
41
51
|
label;
|
|
52
|
+
/** View-local: a {@link SharedSuggestion} is in the document. */
|
|
53
|
+
shared = false;
|
|
42
54
|
#content = $state.raw([]);
|
|
43
55
|
#status = $state('ready');
|
|
44
56
|
#layer;
|
|
@@ -109,9 +121,9 @@ export class Suggestion {
|
|
|
109
121
|
if (this.live)
|
|
110
122
|
this.#status = 'ready';
|
|
111
123
|
};
|
|
112
|
-
/**
|
|
124
|
+
/** Accept it: one command, one undo step, placing it in the document (see {@link Suggestions}). */
|
|
113
125
|
accept = () => this.#layer.accept(this);
|
|
114
|
-
/** Drop it
|
|
126
|
+
/** Drop it (writes nothing). */
|
|
115
127
|
discard = () => this.#layer.drop(this);
|
|
116
128
|
/** Empty it, stream again, and ask the app for another answer (`onRetry`). */
|
|
117
129
|
retry = () => {
|
|
@@ -127,27 +139,33 @@ export class Suggestion {
|
|
|
127
139
|
export class Suggestions {
|
|
128
140
|
edytor;
|
|
129
141
|
#list = $state.raw([]);
|
|
130
|
-
/** Bumped by every change of a suggestion: the surface's passes re-run. */
|
|
142
|
+
/** Bumped by every change of a suggestion (and of the document's): the surface's passes re-run. */
|
|
131
143
|
revision = $state(0);
|
|
144
|
+
/**
|
|
145
|
+
* @internal The shared suggestions plugin's part (`createSharedSuggestionsPlugin`):
|
|
146
|
+
* the list with the shared ones after this view's own.
|
|
147
|
+
*/
|
|
148
|
+
shared;
|
|
149
|
+
/** @internal */
|
|
132
150
|
constructor(edytor) {
|
|
133
151
|
this.edytor = edytor;
|
|
134
152
|
}
|
|
135
|
-
/** Every
|
|
153
|
+
/** Every suggestion, view-local and shared, oldest first (reactive). */
|
|
136
154
|
get list() {
|
|
137
|
-
return this.#list;
|
|
155
|
+
return this.shared?.(this.#list) ?? this.#list;
|
|
138
156
|
}
|
|
139
|
-
/** The newest one: what the keys act on. */
|
|
157
|
+
/** The newest view-local one: what the keys act on. */
|
|
140
158
|
get latest() {
|
|
141
159
|
return this.#list.at(-1);
|
|
142
160
|
}
|
|
143
|
-
get = (id) => this
|
|
161
|
+
get = (id) => this.list.find((s) => s.id === id);
|
|
144
162
|
/**
|
|
145
163
|
* Propose `content` at `at`. Throws when the position names no shown
|
|
146
164
|
* block, a block that holds no children (`inside` a void or a code line),
|
|
147
165
|
* or no selection (`replace: 'selection'` with none).
|
|
148
166
|
*/
|
|
149
167
|
add = (at, content, options = {}) => {
|
|
150
|
-
const resolved = this
|
|
168
|
+
const resolved = this.resolve(at);
|
|
151
169
|
const { facade, document } = this.edytor;
|
|
152
170
|
const anchor = this.#blocks(resolved)[0];
|
|
153
171
|
const parent = 'inside' in resolved ? anchor : (facade.parentOf(anchor) ?? null);
|
|
@@ -327,7 +345,8 @@ export class Suggestions {
|
|
|
327
345
|
const blocks = this.#blocks(at);
|
|
328
346
|
return blocks.length > 0 && blocks.every((id) => this.edytor.facade.isVisibleBlock(id));
|
|
329
347
|
};
|
|
330
|
-
|
|
348
|
+
/** @internal `at` resolved: a selection read, a position checked (throws when it names nothing shown). */
|
|
349
|
+
resolve = (at) => {
|
|
331
350
|
const { edytor } = this;
|
|
332
351
|
const fail = (why) => {
|
|
333
352
|
throw new RangeError(`[edytor] suggestions.add: ${why}`);
|
|
@@ -42,6 +42,11 @@ export declare const ownedOf: (edytor: Edytor, element: Element, entry: {
|
|
|
42
42
|
block: string;
|
|
43
43
|
name?: string;
|
|
44
44
|
owned?: () => Owned;
|
|
45
|
+
handle?: {
|
|
46
|
+
id: string;
|
|
47
|
+
type?: string;
|
|
48
|
+
isEmpty?: boolean;
|
|
49
|
+
};
|
|
45
50
|
} | null) => Owned | null;
|
|
46
51
|
/** Heal `element` against `table`; answers whether anything was written. */
|
|
47
52
|
export declare const heal: (element: HTMLElement, table: Owned) => boolean;
|
|
@@ -63,7 +63,7 @@ export const ownedOf = (edytor, element, entry) => {
|
|
|
63
63
|
style: { display: 'none' }
|
|
64
64
|
};
|
|
65
65
|
if (entry.kind === 'atom') {
|
|
66
|
-
const atom =
|
|
66
|
+
const atom = entry.handle;
|
|
67
67
|
return {
|
|
68
68
|
strict: true,
|
|
69
69
|
owned: {
|
|
@@ -74,7 +74,7 @@ export const ownedOf = (edytor, element, entry) => {
|
|
|
74
74
|
style: {}
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
|
-
const text =
|
|
77
|
+
const text = entry.handle;
|
|
78
78
|
const voided = insideVoid(edytor, block);
|
|
79
79
|
return {
|
|
80
80
|
strict: true,
|
package/dist/surface/cells.d.ts
CHANGED
|
@@ -39,37 +39,14 @@ export type Cells = {
|
|
|
39
39
|
* reports (`onPatch` is told which cells each report replaced).
|
|
40
40
|
*/
|
|
41
41
|
export declare const createCells: (source: CellSource, onPatch?: (patched: Patched, before: Replaced) => void) => Cells;
|
|
42
|
-
export
|
|
43
|
-
type
|
|
44
|
-
kind: 'text';
|
|
45
|
-
}>;
|
|
46
|
-
/** The text between two inline atoms (or a content edge), keyed causally. */
|
|
47
|
-
export type Segment = {
|
|
48
|
-
readonly kind: 'text';
|
|
49
|
-
/** The id of the atom before this segment, or {@link START}. */
|
|
50
|
-
readonly key: string;
|
|
51
|
-
readonly text: string;
|
|
52
|
-
readonly runs: readonly TextRun[];
|
|
53
|
-
};
|
|
42
|
+
export { START, partsOf, type Segment, type AtomPart, type Part } from '../crdt/parts.js';
|
|
43
|
+
import { type Segment } from '../crdt/parts.js';
|
|
54
44
|
/** A suggestion's block as a cell (`components/Suggestion`): rendered like one, never registered. */
|
|
55
45
|
export type PreviewCell = Omit<Cell, 'childIds'> & {
|
|
56
46
|
readonly children: readonly PreviewCell[];
|
|
57
47
|
};
|
|
58
48
|
/** `blocks` as preview cells, keyed by position under `key` (stable while a stream grows). */
|
|
59
49
|
export declare const previewCells: (blocks: readonly JSONBlock[], key: string) => PreviewCell[];
|
|
60
|
-
export type AtomPart = {
|
|
61
|
-
readonly kind: 'inline';
|
|
62
|
-
readonly key: string;
|
|
63
|
-
readonly id: string;
|
|
64
|
-
readonly type: string;
|
|
65
|
-
readonly data: Data;
|
|
66
|
-
};
|
|
67
|
-
export type Part = Segment | AtomPart;
|
|
68
|
-
/**
|
|
69
|
-
* A cell's content as segments and atoms, alternating, starting and ending
|
|
70
|
-
* with a segment (an empty content is one empty `start` segment).
|
|
71
|
-
*/
|
|
72
|
-
export declare const partsOf: (runs: readonly ContentRun[]) => readonly Part[];
|
|
73
50
|
/** Whether a view renders mark `name` (its record has a tag or a snippet): an unknown mark renders its text only. */
|
|
74
51
|
export declare const rendersMark: (marks: ReadonlyMap<string, {
|
|
75
52
|
tag?: unknown;
|
|
@@ -105,4 +82,3 @@ export declare const segmentDeltas: (cell: Cell, segment: Segment, transform?: T
|
|
|
105
82
|
* composition is in it.
|
|
106
83
|
*/
|
|
107
84
|
export declare const placeholderOf: (cell: Cell, composing: boolean) => boolean;
|
|
108
|
-
export {};
|
package/dist/surface/cells.js
CHANGED
|
@@ -125,7 +125,8 @@ export const createCells = (source, onPatch) => {
|
|
|
125
125
|
};
|
|
126
126
|
};
|
|
127
127
|
// ── segments and render deltas ──────────────────────────────────────────
|
|
128
|
-
export
|
|
128
|
+
export { START, partsOf } from '../crdt/parts.js';
|
|
129
|
+
import { partsOf } from '../crdt/parts.js';
|
|
129
130
|
/** `blocks` as preview cells, keyed by position under `key` (stable while a stream grows). */
|
|
130
131
|
export const previewCells = (blocks, key) => blocks.map((block, index) => {
|
|
131
132
|
const id = `${key}.${index}`;
|
|
@@ -139,38 +140,6 @@ export const previewCells = (blocks, key) => blocks.map((block, index) => {
|
|
|
139
140
|
children: Object.freeze(previewCells(block.children ?? [], id))
|
|
140
141
|
});
|
|
141
142
|
});
|
|
142
|
-
const partsMemo = new WeakMap();
|
|
143
|
-
/**
|
|
144
|
-
* A cell's content as segments and atoms, alternating, starting and ending
|
|
145
|
-
* with a segment (an empty content is one empty `start` segment).
|
|
146
|
-
*/
|
|
147
|
-
export const partsOf = (runs) => {
|
|
148
|
-
const memo = partsMemo.get(runs);
|
|
149
|
-
if (memo)
|
|
150
|
-
return memo;
|
|
151
|
-
const parts = [];
|
|
152
|
-
let key = START;
|
|
153
|
-
let segment = [];
|
|
154
|
-
const close = () => parts.push(Object.freeze({
|
|
155
|
-
kind: 'text',
|
|
156
|
-
key,
|
|
157
|
-
text: segment.map((run) => run.text).join(''),
|
|
158
|
-
runs: Object.freeze(segment)
|
|
159
|
-
}));
|
|
160
|
-
for (const run of runs) {
|
|
161
|
-
if (run.kind === 'text') {
|
|
162
|
-
segment.push(run);
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
close();
|
|
166
|
-
parts.push(Object.freeze({ kind: 'inline', key: run.id, id: run.id, type: run.type, data: run.data }));
|
|
167
|
-
key = run.id;
|
|
168
|
-
segment = [];
|
|
169
|
-
}
|
|
170
|
-
close();
|
|
171
|
-
partsMemo.set(runs, Object.freeze(parts));
|
|
172
|
-
return parts;
|
|
173
|
-
};
|
|
174
143
|
/** Whether a view renders mark `name` (its record has a tag or a snippet): an unknown mark renders its text only. */
|
|
175
144
|
export const rendersMark = (marks, name) => {
|
|
176
145
|
const mark = marks.get(name);
|