edytor 4.0.0 → 5.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 +115 -106
- 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/capability.js +9 -13
- 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 +77 -56
- package/dist/crdt/doc/layout.js +5 -5
- package/dist/crdt/doc/meta.js +6 -7
- package/dist/crdt/doc/reads.d.ts +2 -2
- package/dist/crdt/doc/reads.js +18 -28
- package/dist/crdt/doc/steps.js +4 -4
- 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 +355 -333
- package/dist/crdt/edytor-doc.js +125 -82
- 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 +1 -1
- package/dist/crdt/flow.js +4 -10
- package/dist/crdt/index.d.ts +111 -105
- package/dist/crdt/index.js +3 -0
- package/dist/crdt/operations.d.ts +12 -3
- 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.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/comments.d.ts +16 -1
- package/dist/crdt/protocols/comments.js +9 -1
- package/dist/crdt/providers/index.d.ts +6 -4
- package/dist/crdt/providers/index.js +3 -1
- package/dist/crdt/providers/websocket.d.ts +6 -4
- package/dist/crdt/providers/websocket.js +9 -4
- package/dist/crdt/purge.d.ts +7 -1
- package/dist/crdt/purge.js +32 -2
- 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/claims.js +1 -23
- package/dist/crdt/text/index/layout.d.ts +2 -4
- package/dist/crdt/text/index/layout.js +9 -22
- package/dist/crdt/text/index/placement.js +21 -24
- package/dist/crdt/text/index/shared.js +17 -14
- package/dist/crdt/text/model.d.ts +10 -0
- package/dist/crdt/text/model.js +8 -1
- package/dist/crdt/text/runs.d.ts +34 -30
- 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/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 +25 -25
- 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/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 +28 -3
- 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 +11 -37
- 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 +128 -81
- package/dist/surface/pin.svelte.d.ts +9 -9
- package/dist/surface/pin.svelte.js +15 -22
- package/dist/surface/projector.svelte.js +8 -14
- package/dist/text/text.svelte.js +14 -19
- package/dist/themes/notion.css +29 -0
- package/package.json +3 -1
|
@@ -67,6 +67,16 @@ export type CommentRequest = {
|
|
|
67
67
|
block?: string | null;
|
|
68
68
|
/** The first comment's id (default: one the store makes). */
|
|
69
69
|
comment?: string;
|
|
70
|
+
/**
|
|
71
|
+
* The text it is made on, as anchors (`DocAnchor`s, each span's
|
|
72
|
+
* ends): the room writes its anchor mark over them for a user who
|
|
73
|
+
* may only comment (`room.access.comment`); a writer's view writes
|
|
74
|
+
* it itself.
|
|
75
|
+
*/
|
|
76
|
+
anchors?: {
|
|
77
|
+
start: unknown;
|
|
78
|
+
end: unknown;
|
|
79
|
+
}[];
|
|
70
80
|
} | {
|
|
71
81
|
op: 'reply';
|
|
72
82
|
thread: string;
|
|
@@ -85,11 +95,16 @@ export type CommentRequest = {
|
|
|
85
95
|
thread: string;
|
|
86
96
|
comment?: string;
|
|
87
97
|
};
|
|
88
|
-
/**
|
|
98
|
+
/**
|
|
99
|
+
* Who asks: a verified user, read-only or not; a moderator may delete
|
|
100
|
+
* anyone's comment; `anchors`: the store writes a new thread's anchor (a
|
|
101
|
+
* user who may only comment).
|
|
102
|
+
*/
|
|
89
103
|
export type CommentActor = {
|
|
90
104
|
user: string;
|
|
91
105
|
readOnly?: boolean;
|
|
92
106
|
moderator?: boolean;
|
|
107
|
+
anchors?: boolean;
|
|
93
108
|
};
|
|
94
109
|
/** What one request changed (the room broadcasts it, `onComment` receives it). */
|
|
95
110
|
export type CommentChange = {
|
|
@@ -62,13 +62,21 @@ export const parseCommentRequest = (value) => {
|
|
|
62
62
|
return null;
|
|
63
63
|
if (v.block !== undefined && v.block !== null && typeof v.block !== 'string')
|
|
64
64
|
return null;
|
|
65
|
+
if (v.anchors !== undefined &&
|
|
66
|
+
(!Array.isArray(v.anchors) ||
|
|
67
|
+
v.anchors.length > 64 ||
|
|
68
|
+
v.anchors.some((a) => typeof a?.start !== 'object' || typeof a?.end !== 'object')))
|
|
69
|
+
return null;
|
|
65
70
|
return {
|
|
66
71
|
op: 'add',
|
|
67
72
|
thread: v.thread,
|
|
68
73
|
body: v.body,
|
|
69
74
|
...(v.quote === undefined ? {} : { quote: v.quote }),
|
|
70
75
|
...(v.block === undefined ? {} : { block: v.block }),
|
|
71
|
-
...(v.comment === undefined ? {} : { comment: v.comment })
|
|
76
|
+
...(v.comment === undefined ? {} : { comment: v.comment }),
|
|
77
|
+
...(v.anchors === undefined
|
|
78
|
+
? {}
|
|
79
|
+
: { anchors: v.anchors })
|
|
72
80
|
};
|
|
73
81
|
case 'reply':
|
|
74
82
|
if (typeof v.body !== 'string')
|
|
@@ -47,6 +47,8 @@ export type EdytorSyncPayload = {
|
|
|
47
47
|
* websocket sync's local store. `document.attachSync` passes it.
|
|
48
48
|
*/
|
|
49
49
|
attach?: (sync: EdytorSync) => EdytorSyncCleanup | void;
|
|
50
|
+
/** What the room says the document's user may do (`EdytorDocument.access`). */
|
|
51
|
+
access?: (access: 'write' | 'suggest' | 'comment' | 'read') => void;
|
|
50
52
|
};
|
|
51
53
|
export type EdytorSyncCleanup = () => void | Promise<void>;
|
|
52
54
|
export type EdytorSync = ((payload: EdytorSyncPayload) => void | EdytorSyncCleanup) & {
|
|
@@ -273,11 +275,11 @@ export declare const bindProviders: (Y: EngineApi) => {
|
|
|
273
275
|
destroy(): void;
|
|
274
276
|
disconnect(): void;
|
|
275
277
|
connect(): void;
|
|
276
|
-
emit<N extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: N, args: Parameters<WebsocketProviderEvents[N]>): void;
|
|
278
|
+
emit<N extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: N, args: Parameters<WebsocketProviderEvents[N]>): void;
|
|
277
279
|
_observers: Map<string, Set<any>>;
|
|
278
|
-
on<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): WebsocketProviderEvents[NAME];
|
|
279
|
-
once<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
|
|
280
|
-
off<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
|
|
280
|
+
on<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): WebsocketProviderEvents[NAME];
|
|
281
|
+
once<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
|
|
282
|
+
off<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
|
|
281
283
|
};
|
|
282
284
|
};
|
|
283
285
|
watchComments: (server: string | null, roomName: string, listener: (message: import("../protocol.js").CommentMessage) => void) => (() => void);
|
|
@@ -38,7 +38,7 @@ export const bindProviders = (Y) => {
|
|
|
38
38
|
const room = `${serverUrl}/${roomName}`;
|
|
39
39
|
const persistName = options.persist === false ? undefined : (options.persistName ?? `edytor:${room}`);
|
|
40
40
|
const sync = (payload) => {
|
|
41
|
-
const { doc, awareness, synced, failed, attach, armBound, holdBound } = payload;
|
|
41
|
+
const { doc, awareness, synced, failed, attach, armBound, holdBound, access } = payload;
|
|
42
42
|
const { params, WebSocketPolyfill, maxBackoffTime, connectTimeout, disableBc, onExpired, maxFrameBytes } = options;
|
|
43
43
|
const local = persistName !== undefined && typeof indexedDB !== 'undefined'
|
|
44
44
|
? localSync(persistName, { disableBc })
|
|
@@ -54,6 +54,8 @@ export const bindProviders = (Y) => {
|
|
|
54
54
|
});
|
|
55
55
|
if (onExpired)
|
|
56
56
|
provider.on('expired', (state) => onExpired(state));
|
|
57
|
+
if (access)
|
|
58
|
+
provider.on('access', access);
|
|
57
59
|
provider.on('synced', (isSynced) => {
|
|
58
60
|
if (isSynced)
|
|
59
61
|
synced(provider);
|
|
@@ -7,6 +7,8 @@ export type WebsocketProviderEvents = {
|
|
|
7
7
|
status: (event: {
|
|
8
8
|
status: 'connected' | 'disconnected' | 'connecting';
|
|
9
9
|
}) => void;
|
|
10
|
+
/** What the room says this connection's user may do, as it joins (and at a change in place). */
|
|
11
|
+
access: (access: 'write' | 'suggest' | 'comment' | 'read', provider: unknown) => void;
|
|
10
12
|
synced: (state: boolean) => void;
|
|
11
13
|
'connection-close': (event: CloseEvent | null, provider: unknown) => void;
|
|
12
14
|
'connection-error': (event: Event, provider: unknown) => void;
|
|
@@ -221,11 +223,11 @@ export declare const bindWebsocketProvider: (Y: EngineApi) => {
|
|
|
221
223
|
destroy(): void;
|
|
222
224
|
disconnect(): void;
|
|
223
225
|
connect(): void;
|
|
224
|
-
emit<N extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: N, args: Parameters<WebsocketProviderEvents[N]>): void;
|
|
226
|
+
emit<N extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: N, args: Parameters<WebsocketProviderEvents[N]>): void;
|
|
225
227
|
_observers: Map<string, Set<any>>;
|
|
226
|
-
on<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): WebsocketProviderEvents[NAME];
|
|
227
|
-
once<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
|
|
228
|
-
off<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
|
|
228
|
+
on<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): WebsocketProviderEvents[NAME];
|
|
229
|
+
once<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
|
|
230
|
+
off<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
|
|
229
231
|
};
|
|
230
232
|
};
|
|
231
233
|
watchComments: (server: string | null, roomName: string, listener: (message: CommentMessage) => void) => (() => void);
|
|
@@ -52,7 +52,7 @@ import * as math from 'lib0-v14/math';
|
|
|
52
52
|
import * as url from 'lib0-v14/url';
|
|
53
53
|
import { Awareness, removeAwarenessStates } from '../protocols/awareness.js';
|
|
54
54
|
import { IsolatedObservable } from '../protocols/observable.js';
|
|
55
|
-
import { messagePermissionDenied, messageReadOnly, READ_ONLY_DENIAL, readAuthMessage } from '../protocols/auth.js';
|
|
55
|
+
import { messagePermissionDenied, messageAccess, messageReadOnly, READ_ONLY_DENIAL, SUGGEST_ONLY_DENIAL, readAuthMessage } from '../protocols/auth.js';
|
|
56
56
|
import { bindSync } from '../protocols/sync.js';
|
|
57
57
|
import { readCommentsMessage, writeCommentsSubscribe } from '../protocols/comments.js';
|
|
58
58
|
import { frame } from '../protocols/envelope.js';
|
|
@@ -122,12 +122,17 @@ export const bindWebsocketProvider = (Y) => {
|
|
|
122
122
|
if (readOnly)
|
|
123
123
|
provider._writes.deny();
|
|
124
124
|
provider.emit('permission-denied', [reason, provider]);
|
|
125
|
-
|
|
125
|
+
// What a suggest-only socket may not write is stripped: the socket stays.
|
|
126
|
+
if (!readOnly && reason !== SUGGEST_ONLY_DENIAL)
|
|
126
127
|
emitFailed(provider, new Error(`permission denied: ${reason}`));
|
|
127
128
|
},
|
|
128
129
|
// The room's notice at the join: a state, not a refusal (no event).
|
|
129
|
-
() => provider._writes.deny()
|
|
130
|
-
|
|
130
|
+
() => provider._writes.deny(),
|
|
131
|
+
// The room's word at the join, and at a change in place (`read`).
|
|
132
|
+
(access) => provider.emit('access', [access, provider]));
|
|
133
|
+
if (authType !== messagePermissionDenied &&
|
|
134
|
+
authType !== messageReadOnly &&
|
|
135
|
+
authType !== messageAccess) {
|
|
131
136
|
// An auth type we do not speak inside a valid v14 envelope is
|
|
132
137
|
// protocol skew — reported, never dropped silently.
|
|
133
138
|
provider.emit('message-error', [
|
package/dist/crdt/purge.d.ts
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import type { EngineApi, EngineDoc } from './engine-api.js';
|
|
19
19
|
import { type PlacementModel } from './placement/model.js';
|
|
20
20
|
import { type TextEngine } from './text/model.js';
|
|
21
|
+
import { type SuggestFacade } from './doc/suggest.js';
|
|
21
22
|
/** The horizon: the time and encoded state vector of the room's epoch (`room.purge.timing`). */
|
|
22
23
|
export type Horizon = {
|
|
23
24
|
at: number;
|
|
@@ -43,6 +44,11 @@ export type PurgeReport = {
|
|
|
43
44
|
merged: number;
|
|
44
45
|
/** Release records the horizon covers (`hist.release.end`): deleted. */
|
|
45
46
|
released: number;
|
|
47
|
+
/**
|
|
48
|
+
* Suggestions decided before the horizon (`sugg.purge`): nothing of them
|
|
49
|
+
* pending, their records' entries older than it: their records deleted.
|
|
50
|
+
*/
|
|
51
|
+
suggestions: number;
|
|
46
52
|
};
|
|
47
53
|
/**
|
|
48
54
|
* The ended histories a document holds (`hist.release.end`): per writer,
|
|
@@ -53,7 +59,7 @@ export declare const releasesOf: (doc: EngineDoc) => Map<number, number>;
|
|
|
53
59
|
/** The horizon a document holds (`null`: never purged). */
|
|
54
60
|
export declare const readHorizon: (doc: EngineDoc) => Horizon | null;
|
|
55
61
|
export declare const bindPurge: (Y: EngineApi) => {
|
|
56
|
-
purge: (doc: EngineDoc, facade: {
|
|
62
|
+
purge: (doc: EngineDoc, facade: Partial<SuggestFacade> & {
|
|
57
63
|
model: PlacementModel;
|
|
58
64
|
text: TextEngine;
|
|
59
65
|
}, horizon: Horizon | null) => PurgeReport;
|
package/dist/crdt/purge.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { recordsOf, registryOf, view as modelView } from './placement/model.js';
|
|
2
2
|
import { removeUnit, unitOf } from './placement/slots.js';
|
|
3
3
|
import { deleteClaims, purgeStreams, scanText } from './text/model.js';
|
|
4
|
-
import { BLOCK_ATTR_ROOT, DATA, DATA_LEAF_PREFIX, DEL_PREFIX, HORIZON_ATTR, HORIZON_ROOT, REGISTRY_KEY, RELEASE_ROOT, TEXT_DELETES_ROOT, WITHDRAW_PREFIX } from './schema.js';
|
|
4
|
+
import { BLOCK_ATTR_ROOT, DATA, DATA_LEAF_PREFIX, DEL_PREFIX, HORIZON_ATTR, HORIZON_ROOT, REGISTRY_KEY, RELEASE_ROOT, SUGGEST_ROOT, TEXT_DELETES_ROOT, WITHDRAW_PREFIX } from './schema.js';
|
|
5
5
|
import { baseIdOf, isIncarnationId } from './incarnations.js';
|
|
6
|
+
import { suggestionsOf } from './doc/suggest.js';
|
|
6
7
|
import { attrItems, dropSearchMarkers, engineOps, firstItem, holdsIds } from './structs.js';
|
|
7
8
|
const mapOf = (node) => attrItems(node);
|
|
9
|
+
/** The clock a suggestion's record opened at (its writer's clocks it owns start there). */
|
|
10
|
+
const clockOf = (rec) => rec.start ?? 0;
|
|
8
11
|
/**
|
|
9
12
|
* The ended histories a document holds (`hist.release.end`): per writer,
|
|
10
13
|
* the clock its latest live release record ended at (what it wrote below
|
|
@@ -107,7 +110,8 @@ export const bindPurge = (Y) => {
|
|
|
107
110
|
records: 0,
|
|
108
111
|
claims: 0,
|
|
109
112
|
merged: 0,
|
|
110
|
-
released: 0
|
|
113
|
+
released: 0,
|
|
114
|
+
suggestions: 0
|
|
111
115
|
};
|
|
112
116
|
const vector = horizon === null
|
|
113
117
|
? new Map()
|
|
@@ -425,6 +429,32 @@ export const bindPurge = (Y) => {
|
|
|
425
429
|
report.released++;
|
|
426
430
|
}
|
|
427
431
|
}
|
|
432
|
+
// Suggestions decided before the horizon (`sugg.purge`): the decision
|
|
433
|
+
// trail goes with them. A writer's oldest record first, so no record's
|
|
434
|
+
// clocks pass to another.
|
|
435
|
+
const suggest = facade.part && suggestionsOf(facade);
|
|
436
|
+
const records = suggest?.records() ?? [];
|
|
437
|
+
if (records.length > 0) {
|
|
438
|
+
const pending = new Set(suggest.changes().map((c) => c.suggestion));
|
|
439
|
+
const root = doc.get(SUGGEST_ROOT);
|
|
440
|
+
const seen = new Set();
|
|
441
|
+
for (const rec of [...records].sort((a, b) => clockOf(a) - clockOf(b))) {
|
|
442
|
+
if (seen.has(rec.writer))
|
|
443
|
+
continue;
|
|
444
|
+
seen.add(rec.writer);
|
|
445
|
+
const node = root.getAttr(rec.id);
|
|
446
|
+
const items = [node._item];
|
|
447
|
+
for (let it = firstItem(node); it; it = it.right)
|
|
448
|
+
if (!it.deleted)
|
|
449
|
+
items.push(it);
|
|
450
|
+
if (pending.has(rec.id) ||
|
|
451
|
+
!items.every((it) => old(it.id.client, it.id.clock + it.length - 1)))
|
|
452
|
+
continue;
|
|
453
|
+
root.deleteAttr(rec.id);
|
|
454
|
+
seen.delete(rec.writer);
|
|
455
|
+
report.suggestions++;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
428
458
|
// The horizon record, last.
|
|
429
459
|
if (horizon !== null)
|
|
430
460
|
doc.get(HORIZON_ROOT).setAttr(HORIZON_ATTR, { at: horizon.at, sv: horizon.sv });
|
package/dist/crdt/restore.d.ts
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
import type { EngineDoc } from './engine-api.js';
|
|
27
27
|
import { type BlockId, type ContentItem, type InlineSpec, type PlacementModel } from './placement/model.js';
|
|
28
28
|
import type { DataPatch } from './data.js';
|
|
29
|
+
import { type SuggestFacade } from './doc/suggest.js';
|
|
29
30
|
import type { JSONDoc } from '../utils/json.js';
|
|
30
31
|
/** One display unit: a character (a code point) with its marks, or an inline atom, keyed for comparison. */
|
|
31
32
|
export type Unit = {
|
|
@@ -60,7 +61,7 @@ export type RestoreReport = {
|
|
|
60
61
|
deleted: number;
|
|
61
62
|
};
|
|
62
63
|
/** What a restore reads and writes through: the facade over the document (structurally). */
|
|
63
|
-
export type RestoreFacade<Plan> = {
|
|
64
|
+
export type RestoreFacade<Plan> = Partial<SuggestFacade> & {
|
|
64
65
|
model: PlacementModel;
|
|
65
66
|
childSlots(parent: BlockId | null): readonly {
|
|
66
67
|
id: BlockId;
|
package/dist/crdt/restore.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { blockNodeOf, insertSpecsAt, moveBlocksTo, view as modelView } from './placement/model.js';
|
|
2
|
+
import { suggestionsOf } from './doc/suggest.js';
|
|
2
3
|
import { DEL_PREFIX, TYPE, WITHDRAW_PREFIX } from './schema.js';
|
|
3
4
|
import { common, patchWrites, writeData } from './data.js';
|
|
4
5
|
import { canonKey, deleteClaims } from './text/model.js';
|
|
@@ -196,5 +197,13 @@ export const restoreDocument = (doc, facade, json) => {
|
|
|
196
197
|
}
|
|
197
198
|
// 6 · The document's data.
|
|
198
199
|
facade.apply(facade.prepare.patchData(null, [{ path: [], value: json.data ?? {} }]));
|
|
200
|
+
// 7 · Pending suggestions are dropped (`sugg.restore`): the version holds
|
|
201
|
+
// none (its document without them), so what the restore left of them —
|
|
202
|
+
// their proposals — is rejected, in the restore's step (its undo brings
|
|
203
|
+
// them back).
|
|
204
|
+
// (Built over the facade, its shared suggestions: `doc/suggest.ts`.)
|
|
205
|
+
const suggest = facade.part && suggestionsOf(facade);
|
|
206
|
+
if (suggest)
|
|
207
|
+
suggest.run(suggest.decide(suggest.changes(), false));
|
|
199
208
|
return report;
|
|
200
209
|
};
|
package/dist/crdt/schema.d.ts
CHANGED
|
@@ -49,6 +49,14 @@ export declare const HORIZON_ATTR = "h";
|
|
|
49
49
|
* horizon covers.
|
|
50
50
|
*/
|
|
51
51
|
export declare const RELEASE_ROOT = "released";
|
|
52
|
+
/**
|
|
53
|
+
* The shared suggestions (`crdt/doc/suggest.ts`): attr `<id>` is one
|
|
54
|
+
* suggestion's record node, written by its writer (a client id bound to its
|
|
55
|
+
* author): its creation time `at`, `via` and `lbl`, and a list of id lists
|
|
56
|
+
* (fork patch YP31: its writer's proposed deletions, anyone else's settled
|
|
57
|
+
* ranges) and proposed operations. In the history's scope.
|
|
58
|
+
*/
|
|
59
|
+
export declare const SUGGEST_ROOT = "suggest";
|
|
52
60
|
/** Named node roles (`SCHEMA.nodes`). */
|
|
53
61
|
export declare const BLOCK_NODE = "block";
|
|
54
62
|
export declare const CONTENT_NODE = "content";
|
|
@@ -92,7 +100,7 @@ export declare const LAST_CHANGED_ATTR = "l";
|
|
|
92
100
|
export declare const CONTENT = "content";
|
|
93
101
|
/** The schema generation stamped on the `meta` root (`v`) and its manifest name (`schema`). */
|
|
94
102
|
export declare const SCHEMA: {
|
|
95
|
-
readonly version:
|
|
103
|
+
readonly version: 11;
|
|
96
104
|
readonly name: "edytor-doc";
|
|
97
105
|
readonly roots: {
|
|
98
106
|
readonly registry: "blocks";
|
package/dist/crdt/schema.js
CHANGED
|
@@ -42,6 +42,14 @@ export const HORIZON_ATTR = 'h';
|
|
|
42
42
|
* horizon covers.
|
|
43
43
|
*/
|
|
44
44
|
export const RELEASE_ROOT = 'released';
|
|
45
|
+
/**
|
|
46
|
+
* The shared suggestions (`crdt/doc/suggest.ts`): attr `<id>` is one
|
|
47
|
+
* suggestion's record node, written by its writer (a client id bound to its
|
|
48
|
+
* author): its creation time `at`, `via` and `lbl`, and a list of id lists
|
|
49
|
+
* (fork patch YP31: its writer's proposed deletions, anyone else's settled
|
|
50
|
+
* ranges) and proposed operations. In the history's scope.
|
|
51
|
+
*/
|
|
52
|
+
export const SUGGEST_ROOT = 'suggest';
|
|
45
53
|
/** Named node roles (`SCHEMA.nodes`). */
|
|
46
54
|
export const BLOCK_NODE = 'block';
|
|
47
55
|
export const CONTENT_NODE = 'content';
|
|
@@ -86,9 +94,11 @@ export const SCHEMA = {
|
|
|
86
94
|
// YP21), a block has no `claims` list; 9: a block's place is its home slot
|
|
87
95
|
// in its parent's sequence and a move is a move record (fork patch YP22),
|
|
88
96
|
// a block has no `at` map and an array item no rank; 10: the update encoding
|
|
89
|
-
// writes each distinct string once (fork patch YP24)
|
|
90
|
-
//
|
|
91
|
-
|
|
97
|
+
// writes each distinct string once (fork patch YP24); 11: a hold record
|
|
98
|
+
// inside a nested node holds nothing (fork patch YP31: a suggestion's
|
|
99
|
+
// proposals and decisions). A container or frame of an earlier generation
|
|
100
|
+
// is refused.
|
|
101
|
+
version: 11,
|
|
92
102
|
name: 'edytor-doc',
|
|
93
103
|
roots: { registry: REGISTRY_KEY, meta: META_ROOT_KEY },
|
|
94
104
|
metaAttrs: { version: 'v', schema: 'schema' }
|
package/dist/crdt/structs.d.ts
CHANGED
|
@@ -212,6 +212,8 @@ export declare const engineOps: (Y: EngineApi) => {
|
|
|
212
212
|
merge: (sets: IdSetLike[]) => IdSetOf;
|
|
213
213
|
/** Whether a store struct is an item (not a GC or a Skip). */
|
|
214
214
|
isItem: (struct: unknown) => struct is StoreStruct;
|
|
215
|
+
/** Show again what a suggestion's deletes in `tr` proposed (fork patch YP32). */
|
|
216
|
+
unhide: (tr: unknown) => void;
|
|
215
217
|
/** Whether `item` sits in `node`'s subtree. */
|
|
216
218
|
inside: (node: EngineNode, item: StoreStruct) => boolean;
|
|
217
219
|
/** Keep `item` (and its parents) from collection (`keepItem`). */
|
package/dist/crdt/structs.js
CHANGED
|
@@ -238,6 +238,8 @@ export const engineOps = (Y) => {
|
|
|
238
238
|
merge: (sets) => Y.mergeIdSets(sets),
|
|
239
239
|
/** Whether a store struct is an item (not a GC or a Skip). */
|
|
240
240
|
isItem: (struct) => struct instanceof Y.Item,
|
|
241
|
+
/** Show again what a suggestion's deletes in `tr` proposed (fork patch YP32). */
|
|
242
|
+
unhide: (tr) => Y.unhide(tr),
|
|
241
243
|
/** Whether `item` sits in `node`'s subtree. */
|
|
242
244
|
inside: (node, item) => Y.isParentOf(asYNode(node), item),
|
|
243
245
|
/** Keep `item` (and its parents) from collection (`keepItem`). */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEAD,
|
|
1
|
+
import { DEAD, cmpStamp } from '../model.js';
|
|
2
2
|
import { addTo, dropFrom } from './shared.js';
|
|
3
3
|
/** The claim graph of one doc's index, over its state. */
|
|
4
4
|
export const indexClaims = (ix) => {
|
|
@@ -16,7 +16,6 @@ export const indexClaims = (ix) => {
|
|
|
16
16
|
const topInv = new Map();
|
|
17
17
|
/** Claimed block → the blocks whose claims list names it (live or not). */
|
|
18
18
|
const claimersOf = new Map();
|
|
19
|
-
let ownersBuilt = false;
|
|
20
19
|
/** The max-stamp claim on `m` held by a live block. */
|
|
21
20
|
const topClaim = (m) => {
|
|
22
21
|
let best;
|
|
@@ -91,27 +90,6 @@ export const indexClaims = (ix) => {
|
|
|
91
90
|
addTo(displaysMap, to, b);
|
|
92
91
|
};
|
|
93
92
|
const ensureOwners = () => {
|
|
94
|
-
if (!ownersBuilt) {
|
|
95
|
-
ownersBuilt = true;
|
|
96
|
-
ownerSeeds.clear();
|
|
97
|
-
const graph = claimGraph(blocks);
|
|
98
|
-
owners.clear();
|
|
99
|
-
for (const [b, o] of graph.owners)
|
|
100
|
-
if (blocks.has(b))
|
|
101
|
-
owners.set(b, o);
|
|
102
|
-
topOf.clear();
|
|
103
|
-
topInv.clear();
|
|
104
|
-
claimersOf.clear();
|
|
105
|
-
for (const [holder, rec] of blocks)
|
|
106
|
-
for (const c of rec.claims)
|
|
107
|
-
addTo(claimersOf, c.m, holder);
|
|
108
|
-
for (const m of claimersOf.keys())
|
|
109
|
-
setTop(m, topClaim(m));
|
|
110
|
-
displaysMap.clear();
|
|
111
|
-
for (const b of blocks.keys())
|
|
112
|
-
setDisplay(b, undefined, owners.get(b));
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
93
|
if (ownerSeeds.size === 0)
|
|
116
94
|
return;
|
|
117
95
|
// The tops a seed's claims (now, and those it dropped: noted in `claimersOf` changes) decide.
|
|
@@ -4,20 +4,18 @@
|
|
|
4
4
|
* they read.
|
|
5
5
|
*/
|
|
6
6
|
import type { BlockId, ChildSlot } from '../../placement/model.js';
|
|
7
|
-
import type {
|
|
7
|
+
import type { KindRole } from '../runs.js';
|
|
8
8
|
import type { IndexState } from './state.js';
|
|
9
9
|
/** The layout rules of one doc's index, over its state. */
|
|
10
10
|
export declare const indexLayout: (ix: IndexState) => {
|
|
11
|
-
|
|
11
|
+
kindOf: (b: BlockId) => KindRole | undefined;
|
|
12
12
|
lineKind: (b: BlockId) => string | undefined;
|
|
13
13
|
itemKind: (b: BlockId) => string | undefined;
|
|
14
14
|
rowKind: (b: BlockId) => string | undefined;
|
|
15
15
|
rowOfTable: (table: string) => [string, string] | undefined;
|
|
16
|
-
declaredRows: () => Map<string, string>;
|
|
17
16
|
cellKind: (b: BlockId) => string | undefined;
|
|
18
17
|
cellRank: (row: BlockId, id: BlockId) => string | undefined;
|
|
19
18
|
tableFacts: (b: BlockId, data: unknown) => string;
|
|
20
|
-
declaredItems: () => Set<string>;
|
|
21
19
|
dissolve: (kids: Map<BlockId | null, ChildSlot[]>) => Set<BlockId>;
|
|
22
20
|
redissolve: (changed: Map<BlockId, [BlockId | null | undefined, BlockId | null | undefined]>, kinds: Iterable<BlockId>) => Set<BlockId>;
|
|
23
21
|
};
|
|
@@ -3,33 +3,22 @@ import { listedColumns, unlistedRank } from '../../tables.js';
|
|
|
3
3
|
/** The layout rules of one doc's index, over its state. */
|
|
4
4
|
export const indexLayout = (ix) => {
|
|
5
5
|
const { blocks, kindsOf } = ix;
|
|
6
|
-
/**
|
|
7
|
-
const
|
|
6
|
+
/** The role of `b`'s stored kind; `undefined` without roles or a record. */
|
|
7
|
+
const kindOf = (b) => {
|
|
8
8
|
const type = blocks.get(b)?.type;
|
|
9
|
-
return ix.roles === null || type === undefined ? undefined :
|
|
9
|
+
return ix.roles === null || type === undefined ? undefined : ix.roles.of(type);
|
|
10
10
|
};
|
|
11
11
|
/** The line kind of `b` when it is an island declared `lines`. */
|
|
12
|
-
const lineKind = (b) =>
|
|
12
|
+
const lineKind = (b) => kindOf(b)?.line;
|
|
13
13
|
/** The item kind of `b` when it is a layout. */
|
|
14
|
-
const itemKind = (b) =>
|
|
15
|
-
/** The item kinds the roles declare. */
|
|
16
|
-
const declaredItems = () => {
|
|
17
|
-
const items = new Set();
|
|
18
|
-
for (const type of ix.roles?.layoutKinds() ?? [])
|
|
19
|
-
items.add(ix.roles.layout(type));
|
|
20
|
-
return items;
|
|
21
|
-
};
|
|
14
|
+
const itemKind = (b) => kindOf(b)?.layout;
|
|
22
15
|
/** The row kind of `b` when it is a table (`table.*`). */
|
|
23
|
-
const rowKind = (b) =>
|
|
16
|
+
const rowKind = (b) => kindOf(b)?.table;
|
|
24
17
|
/** `table`'s row kind and that row kind's cell kind, as `[row, cell]`. */
|
|
25
18
|
const rowOfTable = (table) => {
|
|
26
|
-
const row = ix.roles?.
|
|
27
|
-
return row === undefined ? undefined : [row, ix.roles.
|
|
19
|
+
const row = ix.roles?.of(table).table;
|
|
20
|
+
return row === undefined ? undefined : [row, ix.roles.of(row).child];
|
|
28
21
|
};
|
|
29
|
-
/** The row kinds (with their cell kinds) the roles declare. */
|
|
30
|
-
const declaredRows = () => new Map([...(ix.roles?.tableKinds() ?? [])]
|
|
31
|
-
.flatMap((type) => rowOfTable(type) ?? [])
|
|
32
|
-
.map(([row, cell]) => [row, cell]));
|
|
33
22
|
/** The cell kind of `b` when it is a row of a table kind. */
|
|
34
23
|
const cellKind = (b) => ix.rowKinds.get(blocks.get(b)?.type ?? '');
|
|
35
24
|
/**
|
|
@@ -255,16 +244,14 @@ export const indexLayout = (ix) => {
|
|
|
255
244
|
return flips;
|
|
256
245
|
};
|
|
257
246
|
return {
|
|
258
|
-
|
|
247
|
+
kindOf,
|
|
259
248
|
lineKind,
|
|
260
249
|
itemKind,
|
|
261
250
|
rowKind,
|
|
262
251
|
rowOfTable,
|
|
263
|
-
declaredRows,
|
|
264
252
|
cellKind,
|
|
265
253
|
cellRank,
|
|
266
254
|
tableFacts,
|
|
267
|
-
declaredItems,
|
|
268
255
|
dissolve,
|
|
269
256
|
redissolve
|
|
270
257
|
};
|
|
@@ -31,7 +31,7 @@ import { engineOps, firstItem } from '../../structs.js';
|
|
|
31
31
|
import { DEAD, displayOf } from '../model.js';
|
|
32
32
|
/** Placement maintenance of one doc's index, over its state, claims, streams and layout rules. */
|
|
33
33
|
export const indexPlacement = (ix) => {
|
|
34
|
-
const { doc, registry, blocks, displaysMap, ownerChanged, placementsMap, following, dirtyLists, leftLists, placementSeeds, stateSeeds, kindsOf, slotsByBlock, rawOrder, edges, fallen, rejected, ensureOwners, ownerOf, top, streamOf, streamsIn, streamAt,
|
|
34
|
+
const { doc, registry, blocks, displaysMap, ownerChanged, placementsMap, following, dirtyLists, leftLists, placementSeeds, stateSeeds, kindsOf, slotsByBlock, rawOrder, edges, fallen, rejected, ensureOwners, ownerOf, top, streamOf, streamsIn, streamAt, kindOf, lineKind, itemKind, rowKind, rowOfTable, cellKind, cellRank, dissolve, redissolve } = ix;
|
|
35
35
|
const kept = engineOps(Y).isKeptReplaced;
|
|
36
36
|
// ── placements and the children index (maintained) ────────────
|
|
37
37
|
/** The document holds a layout or an item kind: `kids0` and `kidsMap` differ. */
|
|
@@ -47,9 +47,9 @@ export const indexPlacement = (ix) => {
|
|
|
47
47
|
const ownShim = {
|
|
48
48
|
ownerOf,
|
|
49
49
|
hidden: (b) => ownerOf(b) !== b || ix.dissolved.has(b),
|
|
50
|
-
childless: (b) =>
|
|
51
|
-
island: (b) =>
|
|
52
|
-
container: (b) =>
|
|
50
|
+
childless: (b) => kindOf(b)?.childless === true,
|
|
51
|
+
island: (b) => kindOf(b)?.island === true,
|
|
52
|
+
container: (b) => kindOf(b)?.container === true,
|
|
53
53
|
lined: (b) => lineKind(b) !== undefined,
|
|
54
54
|
passes: (b) => ix.dissolved.has(b),
|
|
55
55
|
sheds: (owner, child) => {
|
|
@@ -500,14 +500,15 @@ export const indexPlacement = (ix) => {
|
|
|
500
500
|
placementSeeds.clear();
|
|
501
501
|
stateSeeds.clear();
|
|
502
502
|
ownerChanged.clear();
|
|
503
|
-
|
|
503
|
+
const declared = ix.roles?.declared();
|
|
504
|
+
ix.itemKinds = new Set(declared?.items);
|
|
504
505
|
for (const b of blocks.keys()) {
|
|
505
506
|
const item = itemKind(b);
|
|
506
507
|
if (item !== undefined)
|
|
507
508
|
ix.itemKinds.add(item);
|
|
508
509
|
}
|
|
509
|
-
lineKinds = new Set(
|
|
510
|
-
ix.rowKinds =
|
|
510
|
+
lineKinds = new Set(declared?.lines);
|
|
511
|
+
ix.rowKinds = new Map(declared?.rows);
|
|
511
512
|
for (const b of blocks.keys()) {
|
|
512
513
|
const type = blocks.get(b)?.type;
|
|
513
514
|
const rows = type === undefined ? undefined : rowOfTable(type);
|
|
@@ -670,10 +671,8 @@ export const indexPlacement = (ix) => {
|
|
|
670
671
|
*/
|
|
671
672
|
const itemOf = (under, plain) => {
|
|
672
673
|
const parent = typeOf(under);
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
const item = ix.roles.defaultChild(parent);
|
|
676
|
-
return ix.roles.rendersContent(item) ? item : plain;
|
|
674
|
+
const { container, child } = ix.roles.of(parent);
|
|
675
|
+
return container && ix.roles.of(child).rendersContent ? child : plain;
|
|
677
676
|
};
|
|
678
677
|
/**
|
|
679
678
|
* The kind `id` displays as — THE shown-kind rule, read from its slot
|
|
@@ -694,7 +693,8 @@ export const indexPlacement = (ix) => {
|
|
|
694
693
|
const typeOf = (id) => {
|
|
695
694
|
const stored = blocks.get(id)?.type ?? 'unknown';
|
|
696
695
|
const slot = ix.slots.get(id);
|
|
697
|
-
|
|
696
|
+
const roles = ix.roles;
|
|
697
|
+
if (roles === null || slot === undefined)
|
|
698
698
|
return stored;
|
|
699
699
|
const { parent: under, reset } = slot;
|
|
700
700
|
const line = under === null || lineKinds.size === 0 ? undefined : lineKind(under);
|
|
@@ -702,26 +702,23 @@ export const indexPlacement = (ix) => {
|
|
|
702
702
|
return line;
|
|
703
703
|
const lined = lineKinds.has(stored);
|
|
704
704
|
const from = reset === undefined ? undefined : (blocks.get(reset)?.type ?? null);
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
: stored;
|
|
705
|
+
const plain = roles.of(null).child;
|
|
706
|
+
if (!lined && (from === undefined || stored !== roles.of(from).child))
|
|
707
|
+
return stored === plain && under !== null ? itemOf(under, stored) : stored;
|
|
709
708
|
// Out of a removed list, inside an outer list of its kind: still an item. A
|
|
710
709
|
// container whose item is the document's default kind (a column) holds no
|
|
711
710
|
// items of its own: a paragraph under one is no list's item.
|
|
712
|
-
if (!lined && typeof from === 'string' &&
|
|
711
|
+
if (!lined && typeof from === 'string' && roles.of(from).container)
|
|
713
712
|
for (let u = under; u !== null; u = ix.slots.get(u)?.parent ?? null) {
|
|
714
|
-
const t = typeOf(u);
|
|
715
|
-
if (
|
|
716
|
-
ix.roles.defaultChild(t) === stored &&
|
|
717
|
-
stored !== ix.roles.defaultChild(null))
|
|
713
|
+
const t = roles.of(typeOf(u));
|
|
714
|
+
if (t.container && t.child === stored && stored !== plain)
|
|
718
715
|
return stored;
|
|
719
716
|
}
|
|
720
|
-
const kind =
|
|
721
|
-
if (
|
|
717
|
+
const kind = roles.of(under === null ? null : typeOf(under)).child;
|
|
718
|
+
if (roles.of(kind).rendersContent || !roles.of(stored).rendersContent)
|
|
722
719
|
return kind;
|
|
723
720
|
// Never a line kind outside its island, even where the slot's kind shows nothing.
|
|
724
|
-
return lined ?
|
|
721
|
+
return lined ? plain : stored;
|
|
725
722
|
};
|
|
726
723
|
/** The document order, cached until a child list changes. */
|
|
727
724
|
const order = () => (orderCache ??= documentOrder(ix.kidsMap));
|
|
@@ -67,20 +67,23 @@ export const EMPTY_IDS = Object.freeze([]);
|
|
|
67
67
|
* its children.
|
|
68
68
|
*/
|
|
69
69
|
export const sameShape = (roles, a, b) => {
|
|
70
|
-
const lines =
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
70
|
+
const { lines, items, rows, cells } = roles.declared();
|
|
71
|
+
const shape = (t) => {
|
|
72
|
+
const k = roles.of(t);
|
|
73
|
+
return JSON.stringify([
|
|
74
|
+
k.childless,
|
|
75
|
+
k.island,
|
|
76
|
+
k.container,
|
|
77
|
+
k.line,
|
|
78
|
+
lines.has(t),
|
|
79
|
+
k.layout,
|
|
80
|
+
items.has(t),
|
|
81
|
+
k.table,
|
|
82
|
+
rows.has(t),
|
|
83
|
+
cells.has(t)
|
|
84
|
+
]);
|
|
85
|
+
};
|
|
86
|
+
return shape(a) === shape(b);
|
|
84
87
|
};
|
|
85
88
|
/**
|
|
86
89
|
* The index's self-checks, off in production: a test lane's setup installs
|