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.
Files changed (193) hide show
  1. package/README.md +2 -2
  2. package/dist/block/block.svelte.js +6 -21
  3. package/dist/block/inlineBlock.svelte.js +4 -6
  4. package/dist/clipboard/moveText.js +6 -8
  5. package/dist/cloudflare/DocumentRoom.d.ts +10 -3
  6. package/dist/cloudflare/index.d.ts +1 -1
  7. package/dist/cloudflare/room/access.d.ts +8 -3
  8. package/dist/cloudflare/room/access.js +44 -20
  9. package/dist/cloudflare/room/admission.js +28 -5
  10. package/dist/cloudflare/room/comments.js +28 -1
  11. package/dist/cloudflare/room/context.d.ts +106 -109
  12. package/dist/cloudflare/room/context.js +10 -3
  13. package/dist/cloudflare/room/forged.d.ts +14 -0
  14. package/dist/cloudflare/room/forged.js +30 -2
  15. package/dist/cloudflare/room/frames.d.ts +4 -0
  16. package/dist/cloudflare/room/frames.js +5 -1
  17. package/dist/cloudflare/room/purge.js +2 -1
  18. package/dist/cloudflare/room/replicas.d.ts +4 -2
  19. package/dist/cloudflare/room/replicas.js +8 -3
  20. package/dist/cloudflare/room/suggest.d.ts +7 -0
  21. package/dist/cloudflare/room/suggest.js +143 -0
  22. package/dist/cloudflare/routeDocumentSocket.d.ts +10 -1
  23. package/dist/cloudflare/routeDocumentSocket.js +5 -3
  24. package/dist/components/Content.svelte +9 -1
  25. package/dist/crdt/anchors.js +3 -2
  26. package/dist/crdt/attribution/attribution.d.ts +3 -0
  27. package/dist/crdt/attribution/attribution.js +3 -3
  28. package/dist/crdt/doc/funnel.d.ts +1 -1
  29. package/dist/crdt/doc/funnel.js +5 -2
  30. package/dist/crdt/doc/gate.d.ts +1 -1
  31. package/dist/crdt/doc/history.d.ts +6 -0
  32. package/dist/crdt/doc/history.js +79 -59
  33. package/dist/crdt/doc/ops.d.ts +81 -0
  34. package/dist/crdt/doc/ops.js +1898 -0
  35. package/dist/crdt/doc/reads.d.ts +2 -2
  36. package/dist/crdt/doc/reads.js +18 -28
  37. package/dist/crdt/doc/suggest.d.ts +167 -0
  38. package/dist/crdt/doc/suggest.js +579 -0
  39. package/dist/crdt/document.d.ts +10 -1
  40. package/dist/crdt/document.js +19 -11
  41. package/dist/crdt/edytor-doc.d.ts +294 -272
  42. package/dist/crdt/edytor-doc.js +123 -108
  43. package/dist/crdt/engine-api.d.ts +8 -2
  44. package/dist/crdt/engine.d.ts +2 -1
  45. package/dist/crdt/engine.js +3 -1
  46. package/dist/crdt/flow.d.ts +5 -47
  47. package/dist/crdt/flow.js +3 -269
  48. package/dist/crdt/index.d.ts +102 -108
  49. package/dist/crdt/index.js +3 -0
  50. package/dist/crdt/operations.d.ts +13 -4
  51. package/dist/crdt/parts.d.ts +43 -0
  52. package/dist/crdt/parts.js +44 -0
  53. package/dist/crdt/placement/display.d.ts +1 -1
  54. package/dist/crdt/placement/display.js +3 -3
  55. package/dist/crdt/placement/keys.d.ts +5 -0
  56. package/dist/crdt/placement/keys.js +5 -0
  57. package/dist/crdt/placement/resolve.d.ts +1 -1
  58. package/dist/crdt/placement/resolve.js +2 -1
  59. package/dist/crdt/protocol.d.ts +1 -1
  60. package/dist/crdt/protocol.js +1 -1
  61. package/dist/crdt/protocols/auth.d.ts +11 -1
  62. package/dist/crdt/protocols/auth.js +17 -1
  63. package/dist/crdt/protocols/awareness.js +46 -81
  64. package/dist/crdt/protocols/comments.d.ts +16 -1
  65. package/dist/crdt/protocols/comments.js +9 -1
  66. package/dist/crdt/providers/index.d.ts +12 -22
  67. package/dist/crdt/providers/index.js +3 -1
  68. package/dist/crdt/providers/indexeddb.d.ts +6 -10
  69. package/dist/crdt/providers/indexeddb.js +10 -16
  70. package/dist/crdt/providers/room.d.ts +11 -0
  71. package/dist/crdt/providers/room.js +26 -12
  72. package/dist/crdt/providers/websocket.d.ts +8 -12
  73. package/dist/crdt/providers/websocket.js +17 -19
  74. package/dist/crdt/purge.d.ts +7 -1
  75. package/dist/crdt/purge.js +32 -2
  76. package/dist/crdt/rangeDelete.d.ts +2 -47
  77. package/dist/crdt/rangeDelete.js +1 -169
  78. package/dist/crdt/restore.d.ts +2 -1
  79. package/dist/crdt/restore.js +9 -0
  80. package/dist/crdt/schema.d.ts +9 -1
  81. package/dist/crdt/schema.js +13 -3
  82. package/dist/crdt/structs.d.ts +2 -0
  83. package/dist/crdt/structs.js +2 -0
  84. package/dist/crdt/text/index/build.d.ts +125 -0
  85. package/dist/crdt/text/index/build.js +2895 -0
  86. package/dist/crdt/text/index/shared.d.ts +2 -6
  87. package/dist/crdt/text/index/shared.js +19 -19
  88. package/dist/crdt/text/model.d.ts +10 -0
  89. package/dist/crdt/text/model.js +10 -4
  90. package/dist/crdt/text/runs.d.ts +43 -42
  91. package/dist/crdt/text/runs.js +2 -165
  92. package/dist/crdt/vendor/yjs/UPSTREAM.md +83 -0
  93. package/dist/crdt/vendor/yjs/dts/index.d.ts +1 -1
  94. package/dist/crdt/vendor/yjs/dts/structs/Item.d.ts +6 -3
  95. package/dist/crdt/vendor/yjs/dts/ynode.d.ts +13 -2
  96. package/dist/crdt/vendor/yjs/src/index.js +1 -1
  97. package/dist/crdt/vendor/yjs/src/structs/Item.js +44 -5
  98. package/dist/crdt/vendor/yjs/src/ynode.js +29 -7
  99. package/dist/edytor.svelte.d.ts +1 -1
  100. package/dist/edytor.svelte.js +100 -75
  101. package/dist/events/beforeInputCommands.d.ts +2 -2
  102. package/dist/events/domTextOffset.d.ts +2 -0
  103. package/dist/events/domTextOffset.js +8 -0
  104. package/dist/events/nativeInteractiveControl.d.ts +11 -1
  105. package/dist/events/nativeInteractiveControl.js +41 -1
  106. package/dist/events/onBeforeInput.js +3 -3
  107. package/dist/events/onFocus.js +9 -11
  108. package/dist/export/forms.d.ts +75 -75
  109. package/dist/index.d.ts +4 -2
  110. package/dist/index.js +1 -1
  111. package/dist/labels.d.ts +8 -0
  112. package/dist/labels.js +6 -1
  113. package/dist/plugins/comments/CommentsController.svelte.d.ts +5 -1
  114. package/dist/plugins/comments/CommentsController.svelte.js +14 -4
  115. package/dist/plugins/find/search.js +2 -2
  116. package/dist/plugins/index.d.ts +1 -0
  117. package/dist/plugins/index.js +1 -0
  118. package/dist/plugins/richtext/RichTextPlugin.svelte +63 -61
  119. package/dist/plugins/richtext/richTextOperations.js +26 -32
  120. package/dist/plugins/suggestions/SuggestionBars.svelte +40 -23
  121. package/dist/plugins/suggestions/SuggestionBars.svelte.d.ts +3 -1
  122. package/dist/plugins/suggestions/SuggestionRuns.svelte +77 -0
  123. package/dist/plugins/suggestions/SuggestionRuns.svelte.d.ts +9 -0
  124. package/dist/plugins/suggestions/shared.svelte.d.ts +58 -0
  125. package/dist/plugins/suggestions/shared.svelte.js +421 -0
  126. package/dist/plugins/suggestions/suggestionsPlugin.d.ts +15 -4
  127. package/dist/selection/domSelection.d.ts +2 -0
  128. package/dist/selection/domSelection.js +6 -2
  129. package/dist/selection/pointer.svelte.js +75 -48
  130. package/dist/selection/replaceSelection.js +2 -6
  131. package/dist/selection/selection.svelte.d.ts +3 -3
  132. package/dist/selection/selection.svelte.js +39 -32
  133. package/dist/selection/selection.utils.d.ts +14 -0
  134. package/dist/selection/selection.utils.js +33 -11
  135. package/dist/session/commands.js +21 -11
  136. package/dist/session/composition.svelte.d.ts +5 -3
  137. package/dist/session/composition.svelte.js +21 -17
  138. package/dist/session/handles.d.ts +4 -17
  139. package/dist/session/handles.js +14 -44
  140. package/dist/session/navigation.js +6 -9
  141. package/dist/session/selection.d.ts +11 -5
  142. package/dist/session/selection.js +14 -48
  143. package/dist/session/suggestions.svelte.d.ts +38 -9
  144. package/dist/session/suggestions.svelte.js +37 -18
  145. package/dist/session/virtual.d.ts +1 -1
  146. package/dist/surface/attributes.d.ts +5 -0
  147. package/dist/surface/attributes.js +2 -2
  148. package/dist/surface/cells.d.ts +2 -26
  149. package/dist/surface/cells.js +2 -33
  150. package/dist/surface/observer.svelte.d.ts +6 -1
  151. package/dist/surface/observer.svelte.js +143 -111
  152. package/dist/surface/pin.svelte.d.ts +9 -9
  153. package/dist/surface/pin.svelte.js +15 -22
  154. package/dist/surface/projector.svelte.js +10 -16
  155. package/dist/text/text.svelte.js +14 -19
  156. package/dist/themes/notion.css +29 -0
  157. package/dist/utils.d.ts +5 -0
  158. package/dist/utils.js +7 -0
  159. package/package.json +3 -1
  160. package/dist/crdt/doc/capability.d.ts +0 -56
  161. package/dist/crdt/doc/capability.js +0 -211
  162. package/dist/crdt/doc/content.d.ts +0 -16
  163. package/dist/crdt/doc/content.js +0 -77
  164. package/dist/crdt/doc/delete.d.ts +0 -15
  165. package/dist/crdt/doc/delete.js +0 -84
  166. package/dist/crdt/doc/layout.d.ts +0 -9
  167. package/dist/crdt/doc/layout.js +0 -178
  168. package/dist/crdt/doc/meta.d.ts +0 -19
  169. package/dist/crdt/doc/meta.js +0 -199
  170. package/dist/crdt/doc/moves.d.ts +0 -21
  171. package/dist/crdt/doc/moves.js +0 -274
  172. package/dist/crdt/doc/split-merge.d.ts +0 -10
  173. package/dist/crdt/doc/split-merge.js +0 -179
  174. package/dist/crdt/doc/steps.d.ts +0 -65
  175. package/dist/crdt/doc/steps.js +0 -311
  176. package/dist/crdt/text/index/cache.d.ts +0 -16
  177. package/dist/crdt/text/index/cache.js +0 -175
  178. package/dist/crdt/text/index/claims.d.ts +0 -18
  179. package/dist/crdt/text/index/claims.js +0 -225
  180. package/dist/crdt/text/index/fold.d.ts +0 -21
  181. package/dist/crdt/text/index/fold.js +0 -656
  182. package/dist/crdt/text/index/layout.d.ts +0 -24
  183. package/dist/crdt/text/index/layout.js +0 -271
  184. package/dist/crdt/text/index/placement.d.ts +0 -47
  185. package/dist/crdt/text/index/placement.js +0 -741
  186. package/dist/crdt/text/index/records.d.ts +0 -13
  187. package/dist/crdt/text/index/records.js +0 -139
  188. package/dist/crdt/text/index/report.d.ts +0 -18
  189. package/dist/crdt/text/index/report.js +0 -337
  190. package/dist/crdt/text/index/state.d.ts +0 -197
  191. package/dist/crdt/text/index/state.js +0 -116
  192. package/dist/crdt/text/index/streams.d.ts +0 -25
  193. package/dist/crdt/text/index/streams.js +0 -248
@@ -14,10 +14,20 @@ import * as decoding from 'lib0-v14/decoding';
14
14
  export declare const messagePermissionDenied = 0;
15
15
  /** The denial of a read-only socket's write: that socket still syncs (not a terminal refusal). */
16
16
  export declare const READ_ONLY_DENIAL = "read-only";
17
+ /** The denial of what a suggest-only socket wrote that is no suggestion's: it still syncs. */
18
+ export declare const SUGGEST_ONLY_DENIAL = "suggest-only";
17
19
  export declare const writePermissionDenied: (encoder: encoding.Encoder, reason: string) => void;
18
20
  /** The socket may read but not write: sent when it joins, before any write is refused. */
19
21
  export declare const messageReadOnly = 1;
20
22
  export declare const writeReadOnly: (encoder: encoding.Encoder) => void;
23
+ /**
24
+ * The socket's access (the string that follows: `write`, `suggest` or
25
+ * `comment`), sent when it joins, before anything else it reads; a
26
+ * `comment` one's after its read-only notice (a `read` one has only the
27
+ * notice).
28
+ */
29
+ export declare const messageAccess = 2;
30
+ export declare const writeAccess: (encoder: encoding.Encoder, access: string) => void;
21
31
  export type PermissionDeniedHandler = (ydoc: unknown, reason: string) => void;
22
32
  /**
23
33
  * Decode one auth payload. Returns the auth message subtype so the caller
@@ -25,4 +35,4 @@ export type PermissionDeniedHandler = (ydoc: unknown, reason: string) => void;
25
35
  * do not speak is protocol skew, and this stack's contract is that skew is
26
36
  * reported, not silently dropped (same rule as unknown sync subtypes).
27
37
  */
28
- export declare const readAuthMessage: (decoder: decoding.Decoder, y: unknown, permissionDeniedHandler: PermissionDeniedHandler, readOnlyHandler?: (ydoc: unknown) => void) => number;
38
+ export declare const readAuthMessage: (decoder: decoding.Decoder, y: unknown, permissionDeniedHandler: PermissionDeniedHandler, readOnlyHandler?: (ydoc: unknown) => void, accessHandler?: (access: string) => void) => number;
@@ -14,6 +14,8 @@ import * as decoding from 'lib0-v14/decoding';
14
14
  export const messagePermissionDenied = 0;
15
15
  /** The denial of a read-only socket's write: that socket still syncs (not a terminal refusal). */
16
16
  export const READ_ONLY_DENIAL = 'read-only';
17
+ /** The denial of what a suggest-only socket wrote that is no suggestion's: it still syncs. */
18
+ export const SUGGEST_ONLY_DENIAL = 'suggest-only';
17
19
  export const writePermissionDenied = (encoder, reason) => {
18
20
  encoding.writeVarUint(encoder, messagePermissionDenied);
19
21
  encoding.writeVarString(encoder, reason);
@@ -23,13 +25,24 @@ export const messageReadOnly = 1;
23
25
  export const writeReadOnly = (encoder) => {
24
26
  encoding.writeVarUint(encoder, messageReadOnly);
25
27
  };
28
+ /**
29
+ * The socket's access (the string that follows: `write`, `suggest` or
30
+ * `comment`), sent when it joins, before anything else it reads; a
31
+ * `comment` one's after its read-only notice (a `read` one has only the
32
+ * notice).
33
+ */
34
+ export const messageAccess = 2;
35
+ export const writeAccess = (encoder, access) => {
36
+ encoding.writeVarUint(encoder, messageAccess);
37
+ encoding.writeVarString(encoder, access);
38
+ };
26
39
  /**
27
40
  * Decode one auth payload. Returns the auth message subtype so the caller
28
41
  * can surface unknown subtypes — a valid envelope carrying an auth type we
29
42
  * do not speak is protocol skew, and this stack's contract is that skew is
30
43
  * reported, not silently dropped (same rule as unknown sync subtypes).
31
44
  */
32
- export const readAuthMessage = (decoder, y, permissionDeniedHandler, readOnlyHandler) => {
45
+ export const readAuthMessage = (decoder, y, permissionDeniedHandler, readOnlyHandler, accessHandler) => {
33
46
  const authMessageType = decoding.readVarUint(decoder);
34
47
  switch (authMessageType) {
35
48
  case messagePermissionDenied:
@@ -38,6 +51,9 @@ export const readAuthMessage = (decoder, y, permissionDeniedHandler, readOnlyHan
38
51
  case messageReadOnly:
39
52
  // Unclaimed, it is returned like any subtype the caller does not speak.
40
53
  readOnlyHandler?.(y);
54
+ break;
55
+ case messageAccess:
56
+ accessHandler?.(decoding.readVarString(decoder));
41
57
  }
42
58
  return authMessageType;
43
59
  };
@@ -94,41 +94,19 @@ export class Awareness extends IsolatedObservable {
94
94
  }
95
95
  setLocalState(state) {
96
96
  const clientID = this.clientID;
97
- const currLocalMeta = this.meta.get(clientID);
98
- const clock = currLocalMeta === undefined ? 0 : currLocalMeta.clock + 1;
99
97
  const prevState = this.states.get(clientID);
100
- if (state === null) {
101
- this.states.delete(clientID);
102
- }
98
+ put(this, clientID, (this.meta.get(clientID)?.clock ?? -1) + 1, state, time.getUnixTime());
99
+ const changes = noChanges();
100
+ if (state === null)
101
+ changes.removed.push(clientID);
102
+ else if (prevState == null)
103
+ changes.added.push(clientID);
103
104
  else {
104
- this.states.set(clientID, state);
105
- }
106
- this.meta.set(clientID, {
107
- clock,
108
- lastUpdated: time.getUnixTime()
109
- });
110
- const added = [];
111
- const updated = [];
112
- const filteredUpdated = [];
113
- const removed = [];
114
- if (state === null) {
115
- removed.push(clientID);
105
+ changes.updated.push(clientID);
106
+ if (!jsonEquals(prevState, state))
107
+ changes.changed.push(clientID);
116
108
  }
117
- else if (prevState == null) {
118
- if (state != null) {
119
- added.push(clientID);
120
- }
121
- }
122
- else {
123
- updated.push(clientID);
124
- if (!jsonEquals(prevState, state)) {
125
- filteredUpdated.push(clientID);
126
- }
127
- }
128
- if (added.length > 0 || filteredUpdated.length > 0 || removed.length > 0) {
129
- this.emit('change', [{ added, updated: filteredUpdated, removed }, 'local']);
130
- }
131
- this.emit('update', [{ added, updated, removed }, 'local']);
109
+ emitChanges(this, changes, 'local', true);
132
110
  }
133
111
  setLocalStateField(field, value) {
134
112
  const state = this.getLocalState();
@@ -143,30 +121,40 @@ export class Awareness extends IsolatedObservable {
143
121
  return this.states;
144
122
  }
145
123
  }
124
+ const noChanges = () => ({ added: [], updated: [], changed: [], removed: [] });
125
+ /** Write client `clientID`'s `state` (`null`: removed) at `clock`, heard `now`. */
126
+ const put = (awareness, clientID, clock, state, now) => {
127
+ if (state === null)
128
+ awareness.states.delete(clientID);
129
+ else
130
+ awareness.states.set(clientID, state);
131
+ awareness.meta.set(clientID, { clock, lastUpdated: now });
132
+ };
133
+ /**
134
+ * Emit a change: `change` when a client was added, removed or changed its
135
+ * state, `update` when one was touched at all (`always`: even when none was).
136
+ */
137
+ const emitChanges = (awareness, { added, updated, changed, removed }, origin, always = false) => {
138
+ if (added.length > 0 || changed.length > 0 || removed.length > 0)
139
+ awareness.emit('change', [{ added, updated: changed, removed }, origin]);
140
+ if (always || added.length > 0 || updated.length > 0 || removed.length > 0)
141
+ awareness.emit('update', [{ added, updated, removed }, origin]);
142
+ };
146
143
  /**
147
144
  * Mark (remote) clients as inactive and remove them from the list of active
148
145
  * peers. This change is propagated to remote clients.
149
146
  */
150
147
  export const removeAwarenessStates = (awareness, clients, origin) => {
151
- const removed = [];
152
- for (let i = 0; i < clients.length; i++) {
153
- const clientID = clients[i];
148
+ const changes = noChanges();
149
+ for (const clientID of clients) {
154
150
  if (awareness.states.has(clientID)) {
155
151
  awareness.states.delete(clientID);
156
- if (clientID === awareness.clientID) {
157
- const curMeta = awareness.meta.get(clientID);
158
- awareness.meta.set(clientID, {
159
- clock: curMeta.clock + 1,
160
- lastUpdated: time.getUnixTime()
161
- });
162
- }
163
- removed.push(clientID);
152
+ if (clientID === awareness.clientID)
153
+ put(awareness, clientID, awareness.meta.get(clientID).clock + 1, null, time.getUnixTime());
154
+ changes.removed.push(clientID);
164
155
  }
165
156
  }
166
- if (removed.length > 0) {
167
- awareness.emit('change', [{ added: [], updated: [], removed }, origin]);
168
- awareness.emit('update', [{ added: [], updated: [], removed }, origin]);
169
- }
157
+ emitChanges(awareness, changes, origin);
170
158
  };
171
159
  /** The update carrying `clients`' entries of `awareness` (their states in `states`). */
172
160
  export const encodeAwarenessUpdate = (awareness, clients, states = awareness.states) => writeAwarenessEntries(clients.map((clientID) => ({
@@ -202,36 +190,19 @@ export const writeAwarenessEntries = (entries) => encoding.encode((encoder) => {
202
190
  */
203
191
  export const applyAwarenessUpdate = (awareness, update, origin) => {
204
192
  const timestamp = time.getUnixTime();
205
- const added = [];
206
- const updated = [];
207
- const filteredUpdated = [];
208
- const removed = [];
209
- for (const entry of readAwarenessEntries(update)) {
210
- const { clientID, state } = entry;
211
- let { clock } = entry;
193
+ const changes = noChanges();
194
+ const { added, updated, changed, removed } = changes;
195
+ for (const { clientID, clock, state } of readAwarenessEntries(update)) {
212
196
  const clientMeta = awareness.meta.get(clientID);
213
197
  const prevState = awareness.states.get(clientID);
214
198
  const currClock = clientMeta === undefined ? 0 : clientMeta.clock;
215
199
  if (currClock < clock ||
216
200
  (currClock === clock && state === null && awareness.states.has(clientID))) {
217
- if (state === null) {
218
- // never let a remote client remove this local state
219
- if (clientID === awareness.clientID && awareness.getLocalState() != null) {
220
- // remote client removed the local state. Do not remove state. Broadcast a message
221
- // indicating that this client still exists by increasing the clock
222
- clock++;
223
- }
224
- else {
225
- awareness.states.delete(clientID);
226
- }
227
- }
228
- else {
229
- awareness.states.set(clientID, state);
230
- }
231
- awareness.meta.set(clientID, {
232
- clock,
233
- lastUpdated: timestamp
234
- });
201
+ // Never let a remote client remove this local state: keep it and
202
+ // broadcast that this client still exists, by increasing the clock.
203
+ const local = awareness.getLocalState();
204
+ const keep = state === null && clientID === awareness.clientID && local != null;
205
+ put(awareness, clientID, keep ? clock + 1 : clock, keep ? local : state, timestamp);
235
206
  if (clientMeta === undefined && state !== null) {
236
207
  added.push(clientID);
237
208
  }
@@ -239,17 +210,11 @@ export const applyAwarenessUpdate = (awareness, update, origin) => {
239
210
  removed.push(clientID);
240
211
  }
241
212
  else if (state !== null) {
242
- if (!jsonEquals(state, prevState)) {
243
- filteredUpdated.push(clientID);
244
- }
213
+ if (!jsonEquals(state, prevState))
214
+ changed.push(clientID);
245
215
  updated.push(clientID);
246
216
  }
247
217
  }
248
218
  }
249
- if (added.length > 0 || filteredUpdated.length > 0 || removed.length > 0) {
250
- awareness.emit('change', [{ added, updated: filteredUpdated, removed }, origin]);
251
- }
252
- if (added.length > 0 || updated.length > 0 || removed.length > 0) {
253
- awareness.emit('update', [{ added, updated, removed }, origin]);
254
- }
219
+ emitChanges(awareness, changes, origin);
255
220
  };
@@ -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
- /** Who asks: a verified user, read-only or not; a moderator may delete anyone's comment. */
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) & {
@@ -145,17 +147,12 @@ export declare const bindProviders: (Y: EngineApi) => {
145
147
  loadError: unknown;
146
148
  _storeTimeout: number;
147
149
  _storeTimeoutId: ReturnType<typeof setTimeout> | null;
148
- _storeUpdate: (update: Uint8Array, origin: unknown) => void;
150
+ _unlisten: () => void;
149
151
  awareness: Awareness;
150
152
  bcconnected: boolean;
151
153
  disableBc: boolean;
152
154
  _ownsAwareness: boolean;
153
155
  _bcSubscriber: (data: ArrayBuffer | Uint8Array, origin: unknown) => void;
154
- _awarenessUpdateHandler: (updates: {
155
- added: number[];
156
- updated: number[];
157
- removed: number[];
158
- }, origin: unknown) => void;
159
156
  get synced(): boolean;
160
157
  messageHandlers: Record<number, import("./room.js").RoomMessageHandler</*elided*/ any>>;
161
158
  readMessage: (buf: Uint8Array, emitSynced: boolean) => import("lib0-v14/encoding").Encoder;
@@ -163,7 +160,7 @@ export declare const bindProviders: (Y: EngineApi) => {
163
160
  connectBc(): void;
164
161
  disconnectBc(): void;
165
162
  destroy(): Promise<void>;
166
- emit<N extends "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "load-error">(name: N, args: Parameters<{
163
+ emit<N extends "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "load-error">(name: N, args: Parameters<{
167
164
  synced: (provider: /*elided*/ any) => void;
168
165
  'protocol-mismatch': (mismatch: import("./room.js").ProtocolMismatch, provider: /*elided*/ any) => void;
169
166
  'load-error': (error: unknown, provider: /*elided*/ any) => void;
@@ -172,7 +169,7 @@ export declare const bindProviders: (Y: EngineApi) => {
172
169
  failed: (error: unknown, provider: /*elided*/ any) => void;
173
170
  }[N]>): void;
174
171
  _observers: Map<string, Set<any>>;
175
- on<NAME extends "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
172
+ on<NAME extends "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
176
173
  synced: (provider: /*elided*/ any) => void;
177
174
  'protocol-mismatch': (mismatch: import("./room.js").ProtocolMismatch, provider: /*elided*/ any) => void;
178
175
  'load-error': (error: unknown, provider: /*elided*/ any) => void;
@@ -187,7 +184,7 @@ export declare const bindProviders: (Y: EngineApi) => {
187
184
  'schema-mismatch': (detail: import("./room.js").SchemaMismatchDetail, provider: /*elided*/ any) => void;
188
185
  failed: (error: unknown, provider: /*elided*/ any) => void;
189
186
  }[NAME];
190
- once<NAME extends "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
187
+ once<NAME extends "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
191
188
  synced: (provider: /*elided*/ any) => void;
192
189
  'protocol-mismatch': (mismatch: import("./room.js").ProtocolMismatch, provider: /*elided*/ any) => void;
193
190
  'load-error': (error: unknown, provider: /*elided*/ any) => void;
@@ -195,7 +192,7 @@ export declare const bindProviders: (Y: EngineApi) => {
195
192
  'schema-mismatch': (detail: import("./room.js").SchemaMismatchDetail, provider: /*elided*/ any) => void;
196
193
  failed: (error: unknown, provider: /*elided*/ any) => void;
197
194
  }[NAME]): void;
198
- off<NAME extends "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
195
+ off<NAME extends "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
199
196
  synced: (provider: /*elided*/ any) => void;
200
197
  'protocol-mismatch': (mismatch: import("./room.js").ProtocolMismatch, provider: /*elided*/ any) => void;
201
198
  'load-error': (error: unknown, provider: /*elided*/ any) => void;
@@ -256,14 +253,7 @@ export declare const bindProviders: (Y: EngineApi) => {
256
253
  _resync: ReturnType<typeof setInterval> | undefined;
257
254
  _connectTimer: ReturnType<typeof setTimeout> | undefined;
258
255
  _checkInterval: ReturnType<typeof setInterval>;
259
- _updateHandler: (update: Uint8Array, origin: unknown, doc?: unknown, transaction?: {
260
- local: boolean;
261
- }) => void;
262
- _awarenessUpdateHandler: (updates: {
263
- added: number[];
264
- updated: number[];
265
- removed: number[];
266
- }, origin: unknown) => void;
256
+ _unlisten: () => void;
267
257
  get url(): string;
268
258
  get unsaved(): number;
269
259
  get saved(): boolean;
@@ -273,11 +263,11 @@ export declare const bindProviders: (Y: EngineApi) => {
273
263
  destroy(): void;
274
264
  disconnect(): void;
275
265
  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;
266
+ emit<N extends "refused" | "status" | "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: N, args: Parameters<WebsocketProviderEvents[N]>): void;
277
267
  _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;
268
+ on<NAME extends "refused" | "status" | "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): WebsocketProviderEvents[NAME];
269
+ once<NAME extends "refused" | "status" | "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
270
+ off<NAME extends "refused" | "status" | "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
281
271
  };
282
272
  };
283
273
  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);
@@ -45,17 +45,13 @@ export declare const bindIndexeddbProvider: (Y: EngineApi) => {
45
45
  loadError: unknown;
46
46
  _storeTimeout: number;
47
47
  _storeTimeoutId: ReturnType<typeof setTimeout> | null;
48
- _storeUpdate: (update: Uint8Array, origin: unknown) => void;
48
+ /** @internal Removes the document and presence listeners (`room.listen`). */
49
+ _unlisten: () => void;
49
50
  awareness: Awareness;
50
51
  bcconnected: boolean;
51
52
  disableBc: boolean;
52
53
  _ownsAwareness: boolean;
53
54
  _bcSubscriber: (data: ArrayBuffer | Uint8Array, origin: unknown) => void;
54
- _awarenessUpdateHandler: (updates: {
55
- added: number[];
56
- updated: number[];
57
- removed: number[];
58
- }, origin: unknown) => void;
59
55
  /** The lifetime hydration claim. */
60
56
  get synced(): boolean;
61
57
  /**
@@ -78,7 +74,7 @@ export declare const bindIndexeddbProvider: (Y: EngineApi) => {
78
74
  * close the DB. Does NOT remove stored data — `clearDocument` does.
79
75
  */
80
76
  destroy(): Promise<void>;
81
- emit<N extends "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "load-error">(name: N, args: Parameters<{
77
+ emit<N extends "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "load-error">(name: N, args: Parameters<{
82
78
  synced: (provider: /*elided*/ any) => void;
83
79
  'protocol-mismatch': (mismatch: ProtocolMismatch, provider: /*elided*/ any) => void;
84
80
  /** Fired when loading persisted state failed (e.g. a generation mismatch). `whenSynced` rejects. */
@@ -101,7 +97,7 @@ export declare const bindIndexeddbProvider: (Y: EngineApi) => {
101
97
  failed: (error: unknown, provider: /*elided*/ any) => void;
102
98
  }[N]>): void;
103
99
  _observers: Map<string, Set<any>>;
104
- on<NAME extends "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
100
+ on<NAME extends "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
105
101
  synced: (provider: /*elided*/ any) => void;
106
102
  'protocol-mismatch': (mismatch: ProtocolMismatch, provider: /*elided*/ any) => void;
107
103
  /** Fired when loading persisted state failed (e.g. a generation mismatch). `whenSynced` rejects. */
@@ -144,7 +140,7 @@ export declare const bindIndexeddbProvider: (Y: EngineApi) => {
144
140
  */
145
141
  failed: (error: unknown, provider: /*elided*/ any) => void;
146
142
  }[NAME];
147
- once<NAME extends "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
143
+ once<NAME extends "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
148
144
  synced: (provider: /*elided*/ any) => void;
149
145
  'protocol-mismatch': (mismatch: ProtocolMismatch, provider: /*elided*/ any) => void;
150
146
  /** Fired when loading persisted state failed (e.g. a generation mismatch). `whenSynced` rejects. */
@@ -166,7 +162,7 @@ export declare const bindIndexeddbProvider: (Y: EngineApi) => {
166
162
  */
167
163
  failed: (error: unknown, provider: /*elided*/ any) => void;
168
164
  }[NAME]): void;
169
- off<NAME extends "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
165
+ off<NAME extends "failed" | "message-error" | "schema-mismatch" | "protocol-mismatch" | "synced" | "load-error">(name: NAME, f: {
170
166
  synced: (provider: /*elided*/ any) => void;
171
167
  'protocol-mismatch': (mismatch: ProtocolMismatch, provider: /*elided*/ any) => void;
172
168
  /** Fired when loading persisted state failed (e.g. a generation mismatch). `whenSynced` rejects. */
@@ -41,7 +41,7 @@ import { IsolatedObservable } from '../protocols/observable.js';
41
41
  import { bindSync } from '../protocols/sync.js';
42
42
  import { generationDbName } from '../protocols/envelope.js';
43
43
  import { CUSTOM as customStoreName, encodeRow, lastKey, openContainer, readRow, rtop, snapshotRow, storesOf, UPDATES as updatesStoreName, verifyOrStamp } from './container.js';
44
- import { beginDestroy, bindRoomProtocol, tabs, emitFailed, initLifecycle, markSynced, quarantined } from './room.js';
44
+ import { beginDestroy, bindRoomProtocol, tabs, emitFailed, fault, initLifecycle, markSynced, quarantined } from './room.js';
45
45
  export const PREFERRED_TRIM_SIZE = 500;
46
46
  /**
47
47
  * Bind the IndexedDB persistence provider to the vendored v14 engine module.
@@ -175,13 +175,13 @@ export const bindIndexeddbProvider = (Y) => {
175
175
  loadError = null;
176
176
  _storeTimeout;
177
177
  _storeTimeoutId;
178
- _storeUpdate;
178
+ /** @internal Removes the document and presence listeners (`room.listen`). */
179
+ _unlisten;
179
180
  awareness;
180
181
  bcconnected = false;
181
182
  disableBc;
182
183
  _ownsAwareness;
183
184
  _bcSubscriber;
184
- _awarenessUpdateHandler;
185
185
  constructor(name, doc, options = {}) {
186
186
  super();
187
187
  this.doc = doc;
@@ -196,8 +196,6 @@ export const bindIndexeddbProvider = (Y) => {
196
196
  this._db = openContainer(this.dbName);
197
197
  this.destroy = this.destroy.bind(this);
198
198
  initLifecycle(this, this.destroy, () => room.depart(this));
199
- this._bcSubscriber = room.bcSubscriber(this);
200
- this._awarenessUpdateHandler = room.awarenessUpdateHandler(this);
201
199
  // Fail closed: no sync, no BC room join, whenSynced rejects, and
202
200
  // the terminal 'failed' fires — the provider can never reach
203
201
  // synced once loading its persisted state has failed.
@@ -216,7 +214,7 @@ export const bindIndexeddbProvider = (Y) => {
216
214
  // A read-only doc's pre-hydration state is never persisted.
217
215
  const beforeApplyUpdatesCallback = (updatesStore) => {
218
216
  if (!quarantined(doc)) {
219
- rtop(updatesStore.add(encodeRow(Y.encodeStateAsUpdate(doc)))).catch((error) => this.emit('message-error', [error, this]));
217
+ rtop(updatesStore.add(encodeRow(Y.encodeStateAsUpdate(doc)))).catch((error) => fault(this, error));
220
218
  }
221
219
  };
222
220
  // Hydrated: join the room and claim `synced` (lifetime).
@@ -243,7 +241,7 @@ export const bindIndexeddbProvider = (Y) => {
243
241
  .catch(onLoadError);
244
242
  this._storeTimeout = 1000;
245
243
  this._storeTimeoutId = null;
246
- this._storeUpdate = (update, origin) => {
244
+ const storeUpdate = (update, origin) => {
247
245
  // Outbound quarantine: a read-only doc is never persisted
248
246
  // (`broadcastUpdate` applies the same rule to the room).
249
247
  if (origin === this || quarantined(doc))
@@ -253,9 +251,7 @@ export const bindIndexeddbProvider = (Y) => {
253
251
  const [updatesStore] = storesOf(this.db, [updatesStoreName]);
254
252
  // Storage failures must surface — never an unobserved
255
253
  // rejection (the error channel observes every failure mode).
256
- rtop(updatesStore.add(encodeRow(update))).catch((error) => {
257
- this.emit('message-error', [error, this]);
258
- });
254
+ rtop(updatesStore.add(encodeRow(update))).catch((error) => fault(this, error));
259
255
  if (++this._dbsize >= PREFERRED_TRIM_SIZE) {
260
256
  if (this._storeTimeoutId !== null) {
261
257
  clearTimeout(this._storeTimeoutId);
@@ -264,7 +260,7 @@ export const bindIndexeddbProvider = (Y) => {
264
260
  this._storeTimeoutId = null;
265
261
  // The timed caller has no promise consumer — surface
266
262
  // compaction/storage failures on the error channel.
267
- storeState(this, false).catch((error) => this.emit('message-error', [error, this]));
263
+ storeState(this, false).catch((error) => fault(this, error));
268
264
  }, this._storeTimeout);
269
265
  }
270
266
  }
@@ -272,14 +268,13 @@ export const bindIndexeddbProvider = (Y) => {
272
268
  // A synchronous transact failure (e.g. a handle closed
273
269
  // mid-session) still lands on the error channel rather
274
270
  // than propagating into the doc's update dispatch.
275
- this.emit('message-error', [error, this]);
271
+ fault(this, error);
276
272
  }
277
273
  }
278
274
  // Broadcast the update to other tabs
279
275
  room.broadcastUpdate(this, update);
280
276
  };
281
- doc.on('update', this._storeUpdate);
282
- this.awareness.on('update', this._awarenessUpdateHandler);
277
+ this._unlisten = room.listen(this, storeUpdate);
283
278
  const onDocDestroy = () => void this.destroy();
284
279
  destroyWithDoc.set(this, onDocDestroy);
285
280
  doc.on('destroy', onDocDestroy);
@@ -324,11 +319,10 @@ export const bindIndexeddbProvider = (Y) => {
324
319
  if (this._storeTimeoutId) {
325
320
  clearTimeout(this._storeTimeoutId);
326
321
  }
327
- this.doc.off('update', this._storeUpdate);
322
+ this._unlisten();
328
323
  const onDocDestroy = destroyWithDoc.get(this);
329
324
  if (onDocDestroy)
330
325
  this.doc.off('destroy', onDocDestroy);
331
- this.awareness.off('update', this._awarenessUpdateHandler);
332
326
  this.disconnectBc();
333
327
  if (this._ownsAwareness) {
334
328
  this.awareness.destroy();
@@ -164,6 +164,14 @@ export declare const onPageLeave: (fn: () => void, phase: PageLeavePhase) => (()
164
164
  export declare const initLifecycle: (host: LifecycleHost, destroy: () => unknown, depart?: () => void) => void;
165
165
  /** The first sync — lifetime. Returns `true` only the first time. */
166
166
  export declare const markSynced: (host: LifecycleHost) => boolean;
167
+ /** Report `error` on `provider`'s error channel (`'message-error'`): a frame, a payload or a storage failure. */
168
+ export declare const fault: (provider: {
169
+ emit?(event: "message-error", args: [unknown, unknown]): void;
170
+ }, error: unknown) => void;
171
+ /** A message type no handler claims inside a valid envelope: protocol skew, reported, never dropped silently. */
172
+ export declare const unknownType: (provider: {
173
+ emit?(event: "message-error", args: [unknown, unknown]): void;
174
+ }, what: string, type: number) => void;
167
175
  /**
168
176
  * Terminal `'failed'` emission (the sync-failure contract): fires at
169
177
  * most once per provider, only while it has never synced — destroy before
@@ -298,5 +306,8 @@ export declare const bindRoomProtocol: <P extends RoomProvider<P>>(syncProtocol:
298
306
  depart: (provider: P) => void;
299
307
  connectBc: (provider: P & BcMember) => void;
300
308
  disconnectBc: (provider: P & BcMember) => void;
309
+ listen: (provider: P & BcMember, onUpdate: (update: Uint8Array, origin: unknown, doc?: unknown, transaction?: {
310
+ local: boolean;
311
+ }) => void) => (() => void);
301
312
  };
302
313
  export {};