noteloom 0.3.2 → 0.4.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 (104) hide show
  1. package/README.md +344 -53
  2. package/bin/noteloom.mjs +139 -0
  3. package/dist/canvas.cjs +2 -0
  4. package/dist/canvas.cjs.map +1 -0
  5. package/dist/canvas.d.ts +2 -0
  6. package/dist/canvas.js +5 -0
  7. package/dist/canvas.js.map +1 -0
  8. package/dist/collab.cjs +2 -0
  9. package/dist/collab.cjs.map +1 -0
  10. package/dist/collab.d.ts +19 -0
  11. package/dist/collab.js +379 -0
  12. package/dist/collab.js.map +1 -0
  13. package/dist/comments.cjs +2 -0
  14. package/dist/comments.cjs.map +1 -0
  15. package/dist/comments.d.ts +17 -0
  16. package/dist/comments.js +19 -0
  17. package/dist/comments.js.map +1 -0
  18. package/dist/index.cjs +2 -0
  19. package/dist/index.cjs.map +1 -0
  20. package/dist/index.d.ts +392 -32
  21. package/dist/index.js +3983 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/persistence.cjs +2 -0
  24. package/dist/persistence.cjs.map +1 -0
  25. package/dist/persistence.d.ts +11 -0
  26. package/dist/persistence.js +12 -0
  27. package/dist/persistence.js.map +1 -0
  28. package/dist/shared/CommentAvatar-TKqsM8vG.js +53 -0
  29. package/dist/shared/CommentAvatar-TKqsM8vG.js.map +1 -0
  30. package/dist/shared/CommentAvatar-V457IgoQ.cjs +2 -0
  31. package/dist/shared/CommentAvatar-V457IgoQ.cjs.map +1 -0
  32. package/dist/shared/CommentThreadCard-CAJ3oPdr.cjs +2 -0
  33. package/dist/shared/CommentThreadCard-CAJ3oPdr.cjs.map +1 -0
  34. package/dist/shared/CommentThreadCard-Cxf8GwHJ.js +213 -0
  35. package/dist/shared/CommentThreadCard-Cxf8GwHJ.js.map +1 -0
  36. package/dist/shared/CommentsPanel-BwVVyMOC.js +76 -0
  37. package/dist/shared/CommentsPanel-BwVVyMOC.js.map +1 -0
  38. package/dist/shared/CommentsPanel-CES4uzye.cjs +2 -0
  39. package/dist/shared/CommentsPanel-CES4uzye.cjs.map +1 -0
  40. package/dist/shared/VersionHistory-Bg6fufP5.js +1120 -0
  41. package/dist/shared/VersionHistory-Bg6fufP5.js.map +1 -0
  42. package/dist/shared/VersionHistory-DELa2Wbq.cjs +12 -0
  43. package/dist/shared/VersionHistory-DELa2Wbq.cjs.map +1 -0
  44. package/dist/shared/VoiceListeningIndicator-BOfVmLK0.js +338 -0
  45. package/dist/shared/VoiceListeningIndicator-BOfVmLK0.js.map +1 -0
  46. package/dist/shared/VoiceListeningIndicator-DNX8JlQM.cjs +2 -0
  47. package/dist/shared/VoiceListeningIndicator-DNX8JlQM.cjs.map +1 -0
  48. package/dist/shared/fieldRegistry-BoYyW3GH.cjs +2 -0
  49. package/dist/shared/fieldRegistry-BoYyW3GH.cjs.map +1 -0
  50. package/dist/shared/fieldRegistry-PjJWXFoB.js +223 -0
  51. package/dist/shared/fieldRegistry-PjJWXFoB.js.map +1 -0
  52. package/dist/shared/icons-C1_s0jKK.js +4269 -0
  53. package/dist/shared/icons-C1_s0jKK.js.map +1 -0
  54. package/dist/shared/icons-DtCsXuU6.cjs +3531 -0
  55. package/dist/shared/icons-DtCsXuU6.cjs.map +1 -0
  56. package/dist/shared/index-B9jyyL8u.js +2145 -0
  57. package/dist/shared/index-B9jyyL8u.js.map +1 -0
  58. package/dist/shared/index-D9WEXpYM.cjs +2 -0
  59. package/dist/shared/index-D9WEXpYM.cjs.map +1 -0
  60. package/dist/shared/indexedDbPersistence-BKlGPFTP.js +112 -0
  61. package/dist/shared/indexedDbPersistence-BKlGPFTP.js.map +1 -0
  62. package/dist/shared/indexedDbPersistence-BWJ1h5Vp.cjs +2 -0
  63. package/dist/shared/indexedDbPersistence-BWJ1h5Vp.cjs.map +1 -0
  64. package/dist/shared/leafBlockFactory-A9eUhSe-.js +335 -0
  65. package/dist/shared/leafBlockFactory-A9eUhSe-.js.map +1 -0
  66. package/dist/shared/leafBlockFactory-Dp382eP8.cjs +2 -0
  67. package/dist/shared/leafBlockFactory-Dp382eP8.cjs.map +1 -0
  68. package/dist/shared/serialize-DWEfk0vU.cjs +3 -0
  69. package/dist/shared/serialize-DWEfk0vU.cjs.map +1 -0
  70. package/dist/shared/serialize-T9Odwx24.js +86 -0
  71. package/dist/shared/serialize-T9Odwx24.js.map +1 -0
  72. package/dist/shared/starter-kit-CY5wzYDf.js +4980 -0
  73. package/dist/shared/starter-kit-CY5wzYDf.js.map +1 -0
  74. package/dist/shared/starter-kit-lPN-NpJ5.cjs +27 -0
  75. package/dist/shared/starter-kit-lPN-NpJ5.cjs.map +1 -0
  76. package/dist/shared/usePopoverEdgeClamp-CDG26jBM.cjs +2 -0
  77. package/dist/shared/usePopoverEdgeClamp-CDG26jBM.cjs.map +1 -0
  78. package/dist/shared/usePopoverEdgeClamp-gmFm0CnI.js +109 -0
  79. package/dist/shared/usePopoverEdgeClamp-gmFm0CnI.js.map +1 -0
  80. package/dist/shared/useServiceWorkerUpdate-BduabXv6.js +94 -0
  81. package/dist/shared/useServiceWorkerUpdate-BduabXv6.js.map +1 -0
  82. package/dist/shared/useServiceWorkerUpdate-DQboGesi.cjs +2 -0
  83. package/dist/shared/useServiceWorkerUpdate-DQboGesi.cjs.map +1 -0
  84. package/dist/starter-kit.cjs +2 -0
  85. package/dist/starter-kit.cjs.map +1 -0
  86. package/dist/starter-kit.d.ts +9 -0
  87. package/dist/starter-kit.js +10 -0
  88. package/dist/starter-kit.js.map +1 -0
  89. package/dist/style.css +3529 -3200
  90. package/dist/versions.cjs +2 -0
  91. package/dist/versions.cjs.map +1 -0
  92. package/dist/versions.d.ts +11 -0
  93. package/dist/versions.js +13 -0
  94. package/dist/versions.js.map +1 -0
  95. package/dist/voice.cjs +2 -0
  96. package/dist/voice.cjs.map +1 -0
  97. package/dist/voice.d.ts +7 -0
  98. package/dist/voice.js +8 -0
  99. package/dist/voice.js.map +1 -0
  100. package/package.json +159 -92
  101. package/dist/noteloom.cjs +0 -3209
  102. package/dist/noteloom.cjs.map +0 -1
  103. package/dist/noteloom.es.js +0 -14622
  104. package/dist/noteloom.es.js.map +0 -1
@@ -0,0 +1,1120 @@
1
+ import { jsxs as b, Fragment as D, jsx as k } from "react/jsx-runtime";
2
+ import { useState as v, useCallback as Z, useEffect as M, useRef as H, useMemo as $ } from "react";
3
+ import { createPortal as tt } from "react-dom";
4
+ import { g as et, O as m, p as st, q as nt, t as it, f as rt, e as ot, v as ct, c as dt, d as at, w as lt, x as ht, y as ut, o as mt, r as ft, s as kt, m as bt, n as pt, i as yt, j as It, z, l as K, k as _t, a as gt, A as wt, B as Tt, D as Rt, X as Et, R as St } from "./icons-C1_s0jKK.js";
5
+ import { f as Ct, c as V, b as Ot } from "./indexedDbPersistence-BKlGPFTP.js";
6
+ import { c as q, r as vt, s as J } from "./serialize-T9Odwx24.js";
7
+ import { g as xt, H as Pt, F as At, L } from "./fieldRegistry-PjJWXFoB.js";
8
+ import { C as j, f as Mt } from "./CommentAvatar-TKqsM8vG.js";
9
+ function Nt(a, t) {
10
+ const i = Array.from(a ?? ""), e = Array.from(t ?? "");
11
+ let s = 0;
12
+ const n = Math.min(i.length, e.length);
13
+ for (; s < n && i[s] === e[s]; ) s += 1;
14
+ let r = i.length, o = e.length;
15
+ for (; r > s && o > s && i[r - 1] === e[o - 1]; )
16
+ r -= 1, o -= 1;
17
+ return { start: s, oldEnd: r, newEnd: o, oldChars: i, newChars: e };
18
+ }
19
+ const B = "$fieldTypes", R = "$comments", O = "$people";
20
+ class F {
21
+ constructor(t) {
22
+ this.blocks = new Map(((t == null ? void 0 : t.blocks) ?? []).map((i) => [i.id, i])), this.runs = new Map(((t == null ? void 0 : t.runs) ?? []).map((i) => [i.id, i])), this.rootId = (t == null ? void 0 : t.rootId) ?? null, this.fieldTypes = new Map(((t == null ? void 0 : t.fieldTypes) ?? []).map((i) => [i.id, i])), this._fieldTypesSnapshot = null, this.comments = new Map(((t == null ? void 0 : t.comments) ?? []).map((i) => [i.id, i])), this._commentsSnapshot = null, this.people = new Map(((t == null ? void 0 : t.people) ?? []).map((i) => [i.id, i])), this._peopleSnapshot = null, this._listeners = /* @__PURE__ */ new Map(), this._globalListeners = /* @__PURE__ */ new Set(), this._peerId = xt(), this._clock = new Pt(this._peerId), this._fieldClocks = new At(), this._orders = /* @__PURE__ */ new Map(), this._runOrders = /* @__PURE__ */ new Map(), this._runChars = /* @__PURE__ */ new Map(), this._runValueSnapshots = /* @__PURE__ */ new Map();
23
+ }
24
+ /** Lazily seeds a block's ordering CRDT state from its current (plain-array) contentIds the first time it's touched. */
25
+ _getOrder(t) {
26
+ let i = this._orders.get(t);
27
+ if (!i) {
28
+ const e = this.blocks.get(t);
29
+ i = L.fromArray((e == null ? void 0 : e.contentIds) ?? [], { peerId: "legacy" }), this._orders.set(t, i);
30
+ }
31
+ return i;
32
+ }
33
+ /**
34
+ * Lazily seeds a run's character-ordering CRDT state from its current
35
+ * (plain-string) value the first time it's touched. Unlike blocks,
36
+ * individual characters have no pre-existing stable id of their own —
37
+ * BUT a fresh *random* one (genId()) can't be minted here the way
38
+ * `_getRunOrder`'s block equivalent doesn't need to, because a block's
39
+ * seed ids (its real `.id`s) are already identical on every peer (the
40
+ * document itself was already synced); a run's *characters* aren't
41
+ * separately synced at all, only its plain `value` string is. Two peers
42
+ * independently lazy-seeding the same pre-existing text with random ids
43
+ * would each invent a DIFFERENT id for "the same" character, so a
44
+ * remote edit's `afterId` (anchored to the sender's random id) would
45
+ * never resolve on the receiving side. Fix: ids for this initial seed
46
+ * are DETERMINISTIC — `` `${runId}:legacyChar:${index}` `` — so any peer
47
+ * seeding from the same starting string (guaranteed, since a peer only
48
+ * ever needs to seed a run it has already received the full document
49
+ * for) always mints the identical ids independently, with no
50
+ * coordination required. Only ever used for this one-time seed; every
51
+ * character inserted after that (by typing, on any peer) gets a real
52
+ * `genId()`.
53
+ */
54
+ _getRunOrder(t) {
55
+ let i = this._runOrders.get(t);
56
+ if (!i) {
57
+ const e = this.runs.get(t), s = /* @__PURE__ */ new Map();
58
+ i = new L();
59
+ let n = null, r = 0;
60
+ for (const o of Array.from((e == null ? void 0 : e.value) ?? "")) {
61
+ const c = `${t}:legacyChar:${r}`;
62
+ s.set(c, o), i.insert(c, n, { wallTime: 0, counter: 0, peerId: "legacy" }, "legacy"), n = c, r += 1;
63
+ }
64
+ this._runOrders.set(t, i), this._runChars.set(t, s);
65
+ }
66
+ return i;
67
+ }
68
+ /** Materializes a run's current text from its character CRDT, memoized until that run's char order next changes. */
69
+ _materializeRunValue(t) {
70
+ let i = this._runValueSnapshots.get(t);
71
+ if (i === void 0) {
72
+ const e = this._getRunOrder(t), s = this._runChars.get(t);
73
+ i = e.toArray().map((n) => s.get(n)).join(""), this._runValueSnapshots.set(t, i);
74
+ }
75
+ return i;
76
+ }
77
+ /**
78
+ * Diffs `newValue` against the run's current materialized value (common
79
+ * prefix/suffix, code-point aware — see diffCodePoints) and translates
80
+ * only the changed region into character-CRDT insert/delete ops. One
81
+ * shared clock covers the whole edit — matching how a single
82
+ * INSERT_BLOCK/REMOVE_BLOCK op already uses one tick() each — since
83
+ * these characters are never independently reordered relative to each
84
+ * other regardless of how many ticks they'd otherwise get.
85
+ */
86
+ _editRunText(t, i) {
87
+ const e = this._materializeRunValue(t), { start: s, oldEnd: n, newEnd: r, newChars: o } = Nt(e, i), c = this._getRunOrder(t), d = this._runChars.get(t), l = c.toArray(), h = this._clock.tick(), u = [];
88
+ for (let w = s; w < n; w += 1) {
89
+ const g = l[w];
90
+ c.delete(g, h), u.push({ id: g, clock: h });
91
+ }
92
+ const f = [];
93
+ let T = s > 0 ? l[s - 1] : null;
94
+ for (let w = s; w < r; w += 1) {
95
+ const g = et(), _ = o[w];
96
+ d.set(g, _), c.insert(g, T, h, this._peerId), f.push({ id: g, char: _, afterId: T, clock: h }), T = g;
97
+ }
98
+ return this._runValueSnapshots.delete(t), { oldValue: e, insertedChars: f, deletedCharIds: u, clock: h };
99
+ }
100
+ /**
101
+ * Replays a previously-resolved text edit's EXACT character ids (from
102
+ * `op._charEdit`, cached on first application — see the UPDATE_RUN case)
103
+ * instead of re-diffing and minting fresh ones. Needed for `History`'s
104
+ * redo, which replays the same op OBJECT a second time: re-diffing would
105
+ * mint a brand-new set of ids rather than restoring the ones the
106
+ * matching undo had only tombstoned, breaking a subsequent undo/redo
107
+ * cycle (mirrors how INSERT_BLOCK's own handler restores an existing
108
+ * slot via `order.restore()` on redo instead of creating a new one,
109
+ * since a block's id is already stable across replay — characters have
110
+ * no such built-in stable id, so this cache is what gives them one).
111
+ */
112
+ _replayRunTextEdit(t, i) {
113
+ const e = this._getRunOrder(t), s = this._runChars.get(t);
114
+ for (const n of i.insertedChars)
115
+ e.has(n.id) ? e.restore(n.id) : (s.set(n.id, n.char), e.insert(n.id, n.afterId, n.clock, this._peerId));
116
+ for (const n of i.deletedCharIds) e.delete(n.id, n.clock);
117
+ this._runValueSnapshots.delete(t);
118
+ }
119
+ getBlock(t) {
120
+ return this.blocks.get(t);
121
+ }
122
+ getRun(t) {
123
+ return this.runs.get(t);
124
+ }
125
+ /**
126
+ * Persisted, user-created custom select field types (static only) —
127
+ * insertion order. Returns the SAME array reference across calls unless
128
+ * a fieldTypes op ran in between — required for useSyncExternalStore
129
+ * (see useFieldTypes), same reference-stability contract as
130
+ * getBlock/getRun.
131
+ */
132
+ getFieldTypes() {
133
+ return this._fieldTypesSnapshot || (this._fieldTypesSnapshot = [...this.fieldTypes.values()]), this._fieldTypesSnapshot;
134
+ }
135
+ getFieldType(t) {
136
+ return this.fieldTypes.get(t);
137
+ }
138
+ /**
139
+ * Every comment thread — insertion order. Returns the SAME array
140
+ * reference across calls unless a comments op ran in between — required
141
+ * for useSyncExternalStore (see useComments), same contract as
142
+ * getFieldTypes.
143
+ */
144
+ getComments() {
145
+ return this._commentsSnapshot || (this._commentsSnapshot = [...this.comments.values()]), this._commentsSnapshot;
146
+ }
147
+ getComment(t) {
148
+ return this.comments.get(t);
149
+ }
150
+ /**
151
+ * The document's own people list (see src/people/people.js's addPerson —
152
+ * the documented entry point, and createSelectFieldType's "Assignee"
153
+ * usage, which sources its options from this instead of any device-local
154
+ * list, so every collaborator on the same document sees the same
155
+ * assignable people). Same reference-stability contract as getFieldTypes/
156
+ * getComments — required for useSyncExternalStore (see usePeople).
157
+ */
158
+ getPeople() {
159
+ return this._peopleSnapshot || (this._peopleSnapshot = [...this.people.values()]), this._peopleSnapshot;
160
+ }
161
+ getPerson(t) {
162
+ return this.people.get(t);
163
+ }
164
+ /**
165
+ * Every run id in the whole document, regardless of reachability from
166
+ * the root — used by removeCommentMarkEverywhere (see
167
+ * src/comments/commentMarks.js) to strip a deleted comment's id from
168
+ * any run that still carries it, without needing a separate index.
169
+ */
170
+ getAllRunIds() {
171
+ return [...this.runs.keys()];
172
+ }
173
+ getRootId() {
174
+ return this.rootId;
175
+ }
176
+ subscribe(t, i) {
177
+ let e = this._listeners.get(t);
178
+ return e || (e = /* @__PURE__ */ new Set(), this._listeners.set(t, e)), e.add(i), () => {
179
+ e.delete(i), e.size === 0 && this._listeners.delete(t);
180
+ };
181
+ }
182
+ /**
183
+ * Fires on every mutation, local or remote (applyOperation and
184
+ * applyRemoteOperation both funnel through _notify), regardless of
185
+ * which specific id(s) changed — unlike subscribe(id, ...), which is
186
+ * scoped to one id for render-isolation purposes. Meant for
187
+ * whole-document concerns like auto-persistence, not for UI rendering
188
+ * (a component re-rendering on every edit anywhere defeats the point of
189
+ * per-id subscriptions).
190
+ */
191
+ subscribeAll(t) {
192
+ return this._globalListeners.add(t), () => this._globalListeners.delete(t);
193
+ }
194
+ _notify(t) {
195
+ for (const i of t) {
196
+ const e = this._listeners.get(i);
197
+ if (e)
198
+ for (const s of [...e]) s();
199
+ }
200
+ if (this._globalListeners.size > 0)
201
+ for (const i of [...this._globalListeners]) i();
202
+ }
203
+ /** Recursively collects a block (or run) and everything reachable under it. */
204
+ _captureSubtree(t) {
205
+ const i = [], e = [], s = (n) => {
206
+ var o;
207
+ if (this.runs.has(n)) {
208
+ e.push(this.runs.get(n));
209
+ return;
210
+ }
211
+ const r = this.blocks.get(n);
212
+ if (r) {
213
+ i.push(r);
214
+ for (const c of r.contentIds) s(c);
215
+ for (const c of ((o = r.props) == null ? void 0 : o.titleRunIds) ?? []) s(c);
216
+ }
217
+ };
218
+ return s(t), { blocks: i, runs: e };
219
+ }
220
+ _deleteSubtree(t) {
221
+ const i = (e) => {
222
+ var n;
223
+ if (this.runs.has(e)) {
224
+ this.runs.delete(e);
225
+ return;
226
+ }
227
+ const s = this.blocks.get(e);
228
+ if (s) {
229
+ for (const r of s.contentIds) i(r);
230
+ for (const r of ((n = s.props) == null ? void 0 : n.titleRunIds) ?? []) i(r);
231
+ this.blocks.delete(e);
232
+ }
233
+ };
234
+ i(t);
235
+ }
236
+ /**
237
+ * Applies one operation, mutating only the affected map entries, and
238
+ * returns the inverse operation (computed from state captured *before* the
239
+ * mutation, per-op — never reconstructed afterward).
240
+ */
241
+ applyOperation(t) {
242
+ var i;
243
+ switch (t.type) {
244
+ case m.UPDATE_RUN: {
245
+ const e = this.runs.get(t.id), s = Object.keys(t.patch);
246
+ if (s.length === 1 && s[0] === "value") {
247
+ let o = t._charEdit;
248
+ if (o)
249
+ this._replayRunTextEdit(t.id, o);
250
+ else {
251
+ const { oldValue: d, insertedChars: l, deletedCharIds: h, clock: u } = this._editRunText(
252
+ t.id,
253
+ t.patch.value
254
+ );
255
+ o = { oldValue: d, insertedChars: l, deletedCharIds: h, clock: u }, t._charEdit = o;
256
+ }
257
+ const c = this._materializeRunValue(t.id);
258
+ return this.runs.set(t.id, { ...e, value: c }), this._lastEnvelope = {
259
+ kind: "runTextEdit",
260
+ runId: t.id,
261
+ insertedChars: o.insertedChars,
262
+ deletedCharIds: o.deletedCharIds,
263
+ clock: o.clock
264
+ }, this._notify([t.id]), z(
265
+ t.id,
266
+ o.insertedChars.map((d) => d.id),
267
+ // undo: tombstone what was just inserted...
268
+ o.deletedCharIds.map((d) => d.id),
269
+ // ...and restore what was just deleted
270
+ o.oldValue
271
+ // cosmetic-only, for History's caret restoration/change log -- see editRunChars's own doc comment
272
+ );
273
+ }
274
+ const n = {}, r = {};
275
+ for (const o of s)
276
+ n[o] = e[o], r[o] = this._clock.tick(), this._fieldClocks.recordLocal(t.id, o, r[o]);
277
+ return this.runs.set(t.id, { ...e, ...t.patch }), this._lastEnvelope = {
278
+ kind: "fieldWrite",
279
+ target: "run",
280
+ id: t.id,
281
+ patch: t.patch,
282
+ clocks: r
283
+ }, this._notify([t.id]), K(t.id, n);
284
+ }
285
+ case m.EDIT_RUN_CHARS: {
286
+ const e = this._getRunOrder(t.id), s = this._clock.tick();
287
+ for (const o of t.tombstoneIds) e.delete(o, s);
288
+ for (const o of t.restoreIds) e.restore(o);
289
+ this._runValueSnapshots.delete(t.id);
290
+ const n = this.runs.get(t.id), r = this._materializeRunValue(t.id);
291
+ return this.runs.set(t.id, { ...n, value: r }), this._lastEnvelope = {
292
+ kind: "runCharEdit",
293
+ runId: t.id,
294
+ tombstoneIds: t.tombstoneIds,
295
+ restoreIds: t.restoreIds,
296
+ clock: s
297
+ }, this._notify([t.id]), z(t.id, t.restoreIds, t.tombstoneIds);
298
+ }
299
+ case m.UPDATE_BLOCK_PROPS: {
300
+ const e = this.blocks.get(t.id), s = {}, n = {};
301
+ for (const r of Object.keys(t.patch))
302
+ s[r] = e.props[r], n[r] = this._clock.tick(), this._fieldClocks.recordLocal(t.id, `props.${r}`, n[r]);
303
+ return this.blocks.set(t.id, { ...e, props: { ...e.props, ...t.patch } }), this._lastEnvelope = {
304
+ kind: "fieldWrite",
305
+ target: "blockProps",
306
+ id: t.id,
307
+ patch: t.patch,
308
+ clocks: n
309
+ }, this._notify([t.id]), It(t.id, s);
310
+ }
311
+ case m.CHANGE_BLOCK_TYPE: {
312
+ const e = this.blocks.get(t.id), s = e.type, n = e.props, r = this._clock.tick();
313
+ return this._fieldClocks.recordLocal(t.id, "type", r), this.blocks.set(t.id, { ...e, type: t.blockType, props: t.props }), this._lastEnvelope = {
314
+ kind: "fieldWrite",
315
+ target: "blockType",
316
+ id: t.id,
317
+ blockType: t.blockType,
318
+ props: t.props,
319
+ clock: r
320
+ }, this._notify([t.id]), yt(t.id, s, n);
321
+ }
322
+ case m.INSERT_BLOCK: {
323
+ if (t.subtree) {
324
+ for (const n of t.subtree.blocks) this.blocks.set(n.id, n);
325
+ for (const n of t.subtree.runs) this.runs.set(n.id, n);
326
+ } else
327
+ this.blocks.set(t.block.id, t.block);
328
+ const e = this.blocks.get(t.parentId), s = this._getOrder(t.parentId);
329
+ if (s.has(t.block.id))
330
+ s.restore(t.block.id);
331
+ else {
332
+ const n = s.toArray(), r = t.index ?? n.length, o = r > 0 ? n[r - 1] : null;
333
+ s.insert(t.block.id, o, this._clock.tick(), this._peerId);
334
+ }
335
+ return this.blocks.set(t.parentId, { ...e, contentIds: s.toArray() }), this._lastEnvelope = {
336
+ kind: "insertSlot",
337
+ parentId: t.parentId,
338
+ blockId: t.block.id,
339
+ slot: s.getSlot(t.block.id),
340
+ subtree: t.subtree ?? { blocks: [t.block], runs: [] }
341
+ }, this._notify([t.block.id, t.parentId]), pt(t.block.id);
342
+ }
343
+ case m.REMOVE_BLOCK: {
344
+ const e = this.blocks.get(t.id), s = e.parentId, n = this.blocks.get(s), r = this._getOrder(s), o = r.toArray().indexOf(t.id), c = this._captureSubtree(t.id);
345
+ this._deleteSubtree(t.id);
346
+ const d = this._clock.tick();
347
+ return r.delete(t.id, d), this.blocks.set(s, { ...n, contentIds: r.toArray() }), this._lastEnvelope = { kind: "deleteSlot", parentId: s, blockId: t.id, clock: d }, this._notify([s]), { type: m.INSERT_BLOCK, block: e, parentId: s, index: o, subtree: c };
348
+ }
349
+ case m.MOVE_BLOCK: {
350
+ const s = this.blocks.get(t.id).parentId, n = this._getOrder(s), r = n.toArray().indexOf(t.id);
351
+ n.delete(t.id, this._clock.tick()), this.blocks.set(s, {
352
+ ...this.blocks.get(s),
353
+ contentIds: n.toArray()
354
+ });
355
+ const o = this._getOrder(t.toParentId), c = o.toArray(), d = t.toIndex ?? c.length, l = d > 0 ? c[d - 1] : null, h = this._clock.tick();
356
+ o.has(t.id) ? o.move(t.id, l, h, this._peerId) : o.insert(t.id, l, h, this._peerId), this.blocks.set(t.toParentId, {
357
+ ...this.blocks.get(t.toParentId),
358
+ contentIds: o.toArray()
359
+ }), this.blocks.set(t.id, { ...this.blocks.get(t.id), parentId: t.toParentId }), this._lastEnvelope = {
360
+ kind: "moveSlot",
361
+ fromParentId: s,
362
+ toParentId: t.toParentId,
363
+ blockId: t.id,
364
+ slot: o.getSlot(t.id)
365
+ };
366
+ const u = /* @__PURE__ */ new Set([t.id, s, t.toParentId]);
367
+ return this._notify(u), bt(t.id, s, r);
368
+ }
369
+ case m.SET_BLOCK_CONTENT_IDS: {
370
+ const e = this.blocks.get(t.blockId), s = e.contentIds;
371
+ return this.blocks.set(t.blockId, { ...e, contentIds: t.contentIds }), this._orders.set(t.blockId, L.fromArray(t.contentIds, { peerId: "legacy" })), this._lastEnvelope = null, this._notify([t.blockId]), kt(t.blockId, s);
372
+ }
373
+ case m.REPLACE_RUN_SPAN: {
374
+ const e = this.blocks.get(t.blockId), s = e.contentIds.indexOf(t.oldRunIds[0]) !== -1, n = s ? e.contentIds : ((i = e.props) == null ? void 0 : i.titleRunIds) ?? [], r = n.indexOf(t.oldRunIds[0]), o = t.oldRunIds.map((d) => this.runs.get(d));
375
+ for (const d of t.oldRunIds) this.runs.delete(d);
376
+ for (const d of t.newRuns) this.runs.set(d.id, d);
377
+ const c = [...n];
378
+ return c.splice(r, t.oldRunIds.length, ...t.newRuns.map((d) => d.id)), s ? this.blocks.set(t.blockId, { ...e, contentIds: c }) : this.blocks.set(t.blockId, {
379
+ ...e,
380
+ props: { ...e.props, titleRunIds: c }
381
+ }), this._lastEnvelope = null, this._notify([t.blockId, ...t.oldRunIds, ...t.newRuns.map((d) => d.id)]), ft(
382
+ t.blockId,
383
+ t.newRuns.map((d) => d.id),
384
+ o
385
+ );
386
+ }
387
+ case m.SET_BLOCK_RUNS: {
388
+ const e = this.blocks.get(t.blockId), s = e.props && "titleRunIds" in e.props, n = s ? e.props.titleRunIds : e.contentIds, r = n.map((c) => this.runs.get(c)).filter(Boolean);
389
+ for (const c of n) this.runs.delete(c);
390
+ for (const c of t.runs) this.runs.set(c.id, c);
391
+ const o = t.runs.map((c) => c.id);
392
+ return s ? this.blocks.set(t.blockId, {
393
+ ...e,
394
+ props: { ...e.props, titleRunIds: o }
395
+ }) : this.blocks.set(t.blockId, { ...e, contentIds: o }), this._lastEnvelope = null, this._notify([t.blockId, ...n, ...o]), mt(t.blockId, r);
396
+ }
397
+ case m.ADD_FIELD_TYPE:
398
+ return this.fieldTypes.set(t.fieldType.id, t.fieldType), this._fieldTypesSnapshot = null, this._notify([B]), ut(t.fieldType.id);
399
+ case m.UPDATE_FIELD_TYPE: {
400
+ const e = this.fieldTypes.get(t.id), s = {};
401
+ for (const n of Object.keys(t.patch)) s[n] = e[n];
402
+ return this.fieldTypes.set(t.id, { ...e, ...t.patch }), this._fieldTypesSnapshot = null, this._notify([B]), ht(t.id, s);
403
+ }
404
+ case m.REMOVE_FIELD_TYPE: {
405
+ const e = this.fieldTypes.get(t.id);
406
+ return this.fieldTypes.delete(t.id), this._fieldTypesSnapshot = null, this._notify([B]), lt(e);
407
+ }
408
+ case m.ADD_COMMENT_THREAD:
409
+ return this.comments.set(t.thread.id, t.thread), this._commentsSnapshot = null, this._lastEnvelope = { kind: "commentWrite", op: "addThread", thread: t.thread }, this._notify([R]), at(t.thread.id);
410
+ case m.REMOVE_COMMENT_THREAD: {
411
+ const e = this.comments.get(t.commentId);
412
+ return this.comments.delete(t.commentId), this._commentsSnapshot = null, this._lastEnvelope = { kind: "commentWrite", op: "removeThread", commentId: t.commentId }, this._notify([R]), dt(e);
413
+ }
414
+ case m.ADD_COMMENT_REPLY: {
415
+ const e = this.comments.get(t.commentId);
416
+ return this.comments.set(t.commentId, { ...e, messages: [...e.messages, t.message] }), this._commentsSnapshot = null, this._lastEnvelope = {
417
+ kind: "commentWrite",
418
+ op: "addReply",
419
+ commentId: t.commentId,
420
+ message: t.message
421
+ }, this._notify([R]), ct(t.commentId, t.message.id);
422
+ }
423
+ case m.REMOVE_COMMENT_REPLY: {
424
+ const e = this.comments.get(t.commentId), s = e.messages.find((n) => n.id === t.messageId);
425
+ return this.comments.set(t.commentId, {
426
+ ...e,
427
+ messages: e.messages.filter((n) => n.id !== t.messageId)
428
+ }), this._commentsSnapshot = null, this._lastEnvelope = {
429
+ kind: "commentWrite",
430
+ op: "removeReply",
431
+ commentId: t.commentId,
432
+ messageId: t.messageId
433
+ }, this._notify([R]), ot(t.commentId, s);
434
+ }
435
+ case m.RESOLVE_COMMENT: {
436
+ const e = this.comments.get(t.commentId), s = e.resolved;
437
+ return this.comments.set(t.commentId, { ...e, resolved: t.resolved }), this._commentsSnapshot = null, this._lastEnvelope = {
438
+ kind: "commentWrite",
439
+ op: "resolve",
440
+ commentId: t.commentId,
441
+ resolved: t.resolved
442
+ }, this._notify([R]), rt(t.commentId, s);
443
+ }
444
+ case m.ADD_PERSON:
445
+ return this.people.set(t.person.id, t.person), this._peopleSnapshot = null, this._lastEnvelope = { kind: "peopleWrite", op: "add", person: t.person }, this._notify([O]), it(t.person.id);
446
+ case m.UPDATE_PERSON: {
447
+ const e = this.people.get(t.id), s = {};
448
+ for (const n of Object.keys(t.patch)) s[n] = e[n];
449
+ return this.people.set(t.id, { ...e, ...t.patch }), this._peopleSnapshot = null, this._lastEnvelope = { kind: "peopleWrite", op: "update", id: t.id, patch: t.patch }, this._notify([O]), nt(t.id, s);
450
+ }
451
+ case m.REMOVE_PERSON: {
452
+ const e = this.people.get(t.id);
453
+ return this.people.delete(t.id), this._peopleSnapshot = null, this._lastEnvelope = { kind: "peopleWrite", op: "remove", id: t.id }, this._notify([O]), st(e);
454
+ }
455
+ default:
456
+ throw new Error(`Unknown operation type: ${t.type}`);
457
+ }
458
+ }
459
+ /** Applies a batch as one transaction: all mutations first, one notify pass, one combined inverse list. */
460
+ applyOperations(t) {
461
+ return t.map((i) => this.applyOperation(i));
462
+ }
463
+ /**
464
+ * The CRDT-native wire envelope for whatever `applyOperation` just did —
465
+ * read this immediately after each call to broadcast the change to
466
+ * other peers. `null` for op types with no CRDT-safe representation yet
467
+ * (the coarse resync ops: setBlockContentIds/replaceRunSpan/
468
+ * setBlockRuns/fieldType ops) — those remain local-only in this phase.
469
+ */
470
+ getLastEnvelope() {
471
+ return this._lastEnvelope;
472
+ }
473
+ /**
474
+ * Applies a change received from another peer. Deliberately separate
475
+ * from `applyOperation`/`History.perform` — a remote change must never
476
+ * enter the local undo stack (only `perform`/`performBatch` push onto
477
+ * it), so this method is the only way for a remote envelope to reach
478
+ * the store. Idempotent and order-independent per envelope kind: safe
479
+ * to call more than once, or with envelopes arriving out of order.
480
+ */
481
+ applyRemoteOperation(t) {
482
+ var i, e;
483
+ if (t)
484
+ switch (t.kind) {
485
+ case "insertSlot": {
486
+ for (const r of t.subtree.blocks)
487
+ this.blocks.has(r.id) || this.blocks.set(r.id, r);
488
+ for (const r of t.subtree.runs) this.runs.has(r.id) || this.runs.set(r.id, r);
489
+ const s = this._getOrder(t.parentId);
490
+ s.merge([t.slot]);
491
+ const n = this.blocks.get(t.parentId);
492
+ this.blocks.set(t.parentId, { ...n, contentIds: s.toArray() }), this._clock.receive(t.slot.clock), this._notify([t.blockId, t.parentId]);
493
+ return;
494
+ }
495
+ case "deleteSlot": {
496
+ const s = this._getOrder(t.parentId);
497
+ this.blocks.has(t.blockId) && this._deleteSubtree(t.blockId), s.delete(t.blockId, t.clock), t.clock && this._clock.receive(t.clock);
498
+ const n = this.blocks.get(t.parentId);
499
+ this.blocks.set(t.parentId, { ...n, contentIds: s.toArray() }), this._notify([t.parentId]);
500
+ return;
501
+ }
502
+ case "moveSlot": {
503
+ const s = this._getOrder(t.fromParentId);
504
+ s.delete(t.blockId, (i = t.slot) == null ? void 0 : i.clock), this.blocks.set(t.fromParentId, {
505
+ ...this.blocks.get(t.fromParentId),
506
+ contentIds: s.toArray()
507
+ });
508
+ const n = this._getOrder(t.toParentId), r = n.has(t.blockId);
509
+ n.merge([t.slot]), r && n.move(
510
+ t.blockId,
511
+ t.slot.originId,
512
+ t.slot.clock,
513
+ t.slot.peerId
514
+ ), this.blocks.set(t.toParentId, {
515
+ ...this.blocks.get(t.toParentId),
516
+ contentIds: n.toArray()
517
+ }), this.blocks.has(t.blockId) && this.blocks.set(t.blockId, {
518
+ ...this.blocks.get(t.blockId),
519
+ parentId: t.toParentId
520
+ }), this._clock.receive(t.slot.clock), this._notify([t.blockId, t.fromParentId, t.toParentId]);
521
+ return;
522
+ }
523
+ case "fieldWrite": {
524
+ if (t.target === "run") {
525
+ const s = this.runs.get(t.id);
526
+ if (s) {
527
+ const n = {};
528
+ for (const r of Object.keys(t.patch)) {
529
+ const o = t.clocks[r];
530
+ this._fieldClocks.shouldApplyRemote(t.id, r, o) && (n[r] = t.patch[r], this._fieldClocks.recordRemote(t.id, r, o));
531
+ }
532
+ Object.keys(n).length > 0 && (this.runs.set(t.id, { ...s, ...n }), this._notify([t.id]));
533
+ }
534
+ } else if (t.target === "blockProps") {
535
+ const s = this.blocks.get(t.id);
536
+ if (s) {
537
+ const n = {};
538
+ for (const r of Object.keys(t.patch)) {
539
+ const o = t.clocks[r];
540
+ this._fieldClocks.shouldApplyRemote(t.id, `props.${r}`, o) && (n[r] = t.patch[r], this._fieldClocks.recordRemote(t.id, `props.${r}`, o));
541
+ }
542
+ Object.keys(n).length > 0 && (this.blocks.set(t.id, { ...s, props: { ...s.props, ...n } }), this._notify([t.id]));
543
+ }
544
+ } else if (t.target === "blockType") {
545
+ const s = this.blocks.get(t.id);
546
+ s && this._fieldClocks.shouldApplyRemote(t.id, "type", t.clock) && (this._fieldClocks.recordRemote(t.id, "type", t.clock), this.blocks.set(t.id, {
547
+ ...s,
548
+ type: t.blockType,
549
+ props: t.props
550
+ }), this._notify([t.id]));
551
+ }
552
+ for (const s of Object.values(t.clocks ?? {})) this._clock.receive(s);
553
+ t.clock && this._clock.receive(t.clock);
554
+ return;
555
+ }
556
+ case "runTextEdit": {
557
+ const s = this._getRunOrder(t.runId), n = this._runChars.get(t.runId);
558
+ for (const o of t.insertedChars)
559
+ s.has(o.id) || n.set(o.id, o.char), s.insert(o.id, o.afterId, o.clock, (e = o.clock) == null ? void 0 : e.peerId);
560
+ for (const o of t.deletedCharIds) s.delete(o.id, o.clock);
561
+ this._runValueSnapshots.delete(t.runId);
562
+ const r = this.runs.get(t.runId);
563
+ r && (this.runs.set(t.runId, {
564
+ ...r,
565
+ value: this._materializeRunValue(t.runId)
566
+ }), this._notify([t.runId])), this._clock.receive(t.clock);
567
+ return;
568
+ }
569
+ case "runCharEdit": {
570
+ const s = this._getRunOrder(t.runId);
571
+ for (const r of t.tombstoneIds) s.delete(r, t.clock);
572
+ for (const r of t.restoreIds) s.restore(r);
573
+ this._runValueSnapshots.delete(t.runId);
574
+ const n = this.runs.get(t.runId);
575
+ n && (this.runs.set(t.runId, {
576
+ ...n,
577
+ value: this._materializeRunValue(t.runId)
578
+ }), this._notify([t.runId])), this._clock.receive(t.clock);
579
+ return;
580
+ }
581
+ case "commentWrite": {
582
+ if (t.op === "addThread")
583
+ this.comments.has(t.thread.id) || (this.comments.set(t.thread.id, t.thread), this._commentsSnapshot = null, this._notify([R]));
584
+ else if (t.op === "removeThread")
585
+ this.comments.delete(t.commentId) && (this._commentsSnapshot = null, this._notify([R]));
586
+ else if (t.op === "addReply") {
587
+ const s = this.comments.get(t.commentId);
588
+ s && !s.messages.some((n) => n.id === t.message.id) && (this.comments.set(t.commentId, {
589
+ ...s,
590
+ messages: [...s.messages, t.message]
591
+ }), this._commentsSnapshot = null, this._notify([R]));
592
+ } else if (t.op === "removeReply") {
593
+ const s = this.comments.get(t.commentId);
594
+ s && s.messages.some((n) => n.id === t.messageId) && (this.comments.set(t.commentId, {
595
+ ...s,
596
+ messages: s.messages.filter((n) => n.id !== t.messageId)
597
+ }), this._commentsSnapshot = null, this._notify([R]));
598
+ } else if (t.op === "resolve") {
599
+ const s = this.comments.get(t.commentId);
600
+ s && (this.comments.set(t.commentId, { ...s, resolved: t.resolved }), this._commentsSnapshot = null, this._notify([R]));
601
+ }
602
+ return;
603
+ }
604
+ case "peopleWrite": {
605
+ if (t.op === "add")
606
+ this.people.has(t.person.id) || (this.people.set(t.person.id, t.person), this._peopleSnapshot = null, this._notify([O]));
607
+ else if (t.op === "update") {
608
+ const s = this.people.get(t.id);
609
+ s && (this.people.set(t.id, { ...s, ...t.patch }), this._peopleSnapshot = null, this._notify([O]));
610
+ } else t.op === "remove" && this.people.delete(t.id) && (this._peopleSnapshot = null, this._notify([O]));
611
+ return;
612
+ }
613
+ default:
614
+ throw new Error(`Unknown remote envelope kind: ${t.kind}`);
615
+ }
616
+ }
617
+ /** Total tombstoned (deleted but still retained) slots across every parent's ordering CRDT and every run's character CRDT — a cheap signal for whether pruneTombstones() is worth calling, or just for observing growth over a long session. */
618
+ getTombstoneCount() {
619
+ let t = 0;
620
+ for (const i of this._orders.values()) t += i.tombstoneCount();
621
+ for (const i of this._runOrders.values()) t += i.tombstoneCount();
622
+ return t;
623
+ }
624
+ /**
625
+ * Permanently removes tombstones older than `maxAgeMs` (default 24h)
626
+ * across every parent's ordering CRDT — see ListCrdtState.pruneTombstones
627
+ * for the safety argument this relies on (this package's reconnect model
628
+ * always resyncs via a full snapshot, never a replayed op log, which is
629
+ * what makes a time-based threshold safe here specifically). Opt-in:
630
+ * never called automatically by anything in this store — call it
631
+ * yourself on whatever schedule fits (see createPeriodicTombstoneGC for
632
+ * a ready-made "just handle it" helper). Returns the number of slots
633
+ * removed.
634
+ */
635
+ pruneTombstones({ maxAgeMs: t = 24 * 60 * 60 * 1e3, now: i = Date.now() } = {}) {
636
+ const e = { wallTime: i - t, counter: 0, peerId: "" };
637
+ let s = 0;
638
+ for (const n of this._orders.values()) s += n.pruneTombstones(e);
639
+ for (const n of this._runOrders.values()) s += n.pruneTombstones(e);
640
+ return s;
641
+ }
642
+ toJSON() {
643
+ return {
644
+ blocks: [...this.blocks.values()],
645
+ runs: [...this.runs.values()],
646
+ rootId: this.rootId,
647
+ fieldTypes: [...this.fieldTypes.values()],
648
+ comments: [...this.comments.values()],
649
+ people: [...this.people.values()]
650
+ };
651
+ }
652
+ static fromJSON(t) {
653
+ return new F(t);
654
+ }
655
+ }
656
+ function Dt(a, t) {
657
+ if (typeof a.performBatch == "function") a.performBatch(t);
658
+ else for (const i of t) a.applyOperation(i);
659
+ }
660
+ function ne(a, t) {
661
+ return { roots: t.map((i) => q(a, i)) };
662
+ }
663
+ function Lt(a, t, { parentId: i, index: e }) {
664
+ const s = [];
665
+ let n = e;
666
+ for (const r of t.roots) {
667
+ const { block: o, runs: c, subtreeBlocks: d } = vt(r);
668
+ o.parentId = i, s.push(_t(o, i, n, { blocks: [o, ...d], runs: c })), n += 1;
669
+ }
670
+ Dt(a, s);
671
+ }
672
+ function Bt(a, t) {
673
+ const i = a.store ?? a;
674
+ i.blocks = new Map((t.blocks ?? []).map((e) => [e.id, e])), i.runs = new Map((t.runs ?? []).map((e) => [e.id, e])), i.rootId = t.rootId ?? null, i.fieldTypes = new Map((t.fieldTypes ?? []).map((e) => [e.id, e])), i._fieldTypesSnapshot = null, i.comments = new Map((t.comments ?? []).map((e) => [e.id, e])), i._commentsSnapshot = null, i._orders = /* @__PURE__ */ new Map(), i._notify([
675
+ ...i.blocks.keys(),
676
+ ...i.runs.keys(),
677
+ "$fieldTypes",
678
+ "$comments"
679
+ ]);
680
+ }
681
+ function ie(a, t) {
682
+ for (const i of t)
683
+ a.register(`__blockTemplate:${i.id}`, {
684
+ component: () => null,
685
+ isLeaf: !0,
686
+ slashCommand: {
687
+ label: i.label,
688
+ icon: i.icon,
689
+ keywords: i.keywords ?? [i.label],
690
+ run(e, { blockId: s, runId: n, sliceStart: r, sliceEnd: o }) {
691
+ const c = e.getRun(n), d = (c == null ? void 0 : c.value) ?? "";
692
+ e.applyOperation(
693
+ K(n, { value: d.slice(0, r) + d.slice(o) })
694
+ );
695
+ const l = e.getBlock(s), u = e.getBlock(l.parentId).contentIds.indexOf(s) + 1;
696
+ Lt(e, i, { parentId: l.parentId, index: u });
697
+ }
698
+ }
699
+ });
700
+ }
701
+ function Vt(a, { pretty: t = !0 } = {}) {
702
+ const i = a.getRootId(), { blocks: e, runs: s } = q(a, i);
703
+ return JSON.stringify({ version: 1, rootId: i, blocks: e, runs: s }, null, t ? 2 : 0);
704
+ }
705
+ function X(a, t, i) {
706
+ const e = a.getBlock(a.getRootId());
707
+ return e ? J(a, t, e.contentIds, i).html : "";
708
+ }
709
+ function re(a, t, i) {
710
+ const e = a.getBlock(a.getRootId());
711
+ return e ? J(a, t, e.contentIds, i).text : "";
712
+ }
713
+ function oe(a, t, i) {
714
+ return `<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns="http://www.w3.org/TR/REC-html40">
715
+ <head>
716
+ <meta charset="utf-8">
717
+ <title>Document</title>
718
+ <!--[if gte mso 9]><xml><w:WordDocument><w:View>Print</w:View><w:DoNotOptimizeForBrowser/></w:WordDocument></xml><![endif]-->
719
+ </head>
720
+ <body>${X(a, t, i)}</body>
721
+ </html>`;
722
+ }
723
+ function ce(a, t, i) {
724
+ const e = a.getBlock(a.getRootId());
725
+ if (!e) return "";
726
+ const s = { store: a, registry: t, inlineRegistry: i }, n = e.contentIds;
727
+ let r = "", o = null;
728
+ for (const c of n) {
729
+ const d = a.getBlock(c), l = t.get(d.type), h = (l.toMarkdown ?? l.toPlainText)(d, s);
730
+ if (o) {
731
+ const u = d.type === "listItem" && o.type === "listItem" && !!d.props.ordered == !!o.props.ordered, f = d.type === "blockquote" && o.type === "blockquote";
732
+ r += u || f ? `
733
+ ` : `
734
+
735
+ `;
736
+ }
737
+ r += h, o = d;
738
+ }
739
+ return r;
740
+ }
741
+ const Ft = 5 * 60 * 1e3, Ht = 200;
742
+ function $t({
743
+ store: a,
744
+ docId: t,
745
+ idleMs: i = Ft,
746
+ maxVersions: e = Ht,
747
+ onSnapshot: s,
748
+ onError: n
749
+ }) {
750
+ let r = null, o = a.getHistoryLog().length, c = !1;
751
+ function d() {
752
+ r && (clearTimeout(r), r = null);
753
+ }
754
+ async function l() {
755
+ if (d(), !c) return;
756
+ c = !1;
757
+ const u = a.getHistoryLog(), f = u.slice(o);
758
+ if (o = u.length, f.length === 0) return;
759
+ const T = new Set(f.map((_) => _.id).filter(Boolean)), w = [...new Set(f.map((_) => _.actorId).filter(Boolean))], g = f[f.length - 1].actorId ?? null;
760
+ try {
761
+ const _ = JSON.parse(Vt(a)), S = {
762
+ id: crypto.randomUUID(),
763
+ docId: t,
764
+ timestamp: Date.now(),
765
+ authorId: g,
766
+ authorIds: w,
767
+ summary: `${T.size} block${T.size === 1 ? "" : "s"} changed`,
768
+ doc: _
769
+ };
770
+ await Ct(S), s == null || s(S);
771
+ const y = await V(t);
772
+ if (y.length > e) {
773
+ const C = y.slice(e);
774
+ await Promise.all(C.map((N) => Ot(N.id)));
775
+ }
776
+ } catch (_) {
777
+ n == null || n(_);
778
+ }
779
+ }
780
+ const h = a.subscribeToHistory(() => {
781
+ c = !0, d(), r = setTimeout(l, i);
782
+ });
783
+ return {
784
+ /** Stops listening. Does NOT flush a final in-progress window -- call `flush()` first if you want that (e.g. right before navigating away). */
785
+ stop() {
786
+ d(), h();
787
+ },
788
+ /** Closes and saves the current window immediately, without waiting for `idleMs` of inactivity. A no-op if nothing's happened since the last save. */
789
+ flush: l
790
+ };
791
+ }
792
+ function zt(a, t) {
793
+ const i = W(a), e = W(t), s = i.length, n = e.length, r = Array.from({ length: s + 1 }, () => new Uint32Array(n + 1));
794
+ for (let h = s - 1; h >= 0; h -= 1)
795
+ for (let u = n - 1; u >= 0; u -= 1)
796
+ r[h][u] = i[h] === e[u] ? r[h + 1][u + 1] + 1 : Math.max(r[h + 1][u], r[h][u + 1]);
797
+ const o = [], c = (h, u) => {
798
+ const f = o[o.length - 1];
799
+ f && f.type === h ? f.text += u : o.push({ type: h, text: u });
800
+ };
801
+ let d = 0, l = 0;
802
+ for (; d < s && l < n; )
803
+ i[d] === e[l] ? (c("equal", i[d]), d += 1, l += 1) : r[d + 1][l] >= r[d][l + 1] ? (c("removed", i[d]), d += 1) : (c("added", e[l]), l += 1);
804
+ for (; d < s; )
805
+ c("removed", i[d]), d += 1;
806
+ for (; l < n; )
807
+ c("added", e[l]), l += 1;
808
+ return o;
809
+ }
810
+ function W(a) {
811
+ return (a ?? "").match(/\s+|[^\s]+/g) ?? [];
812
+ }
813
+ const jt = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };
814
+ function A(a) {
815
+ return a.replace(/[&<>"']/g, (t) => jt[t]);
816
+ }
817
+ function U(a) {
818
+ const t = new Map(a.blocks.map((r) => [r.id, r])), i = new Map(a.runs.map((r) => [r.id, r])), e = [], s = (r) => r.map((o) => {
819
+ var c;
820
+ return ((c = i.get(o)) == null ? void 0 : c.value) ?? "";
821
+ }).join(""), n = (r) => {
822
+ var d;
823
+ const o = t.get(r);
824
+ if (!o) return;
825
+ const c = (d = o.props) == null ? void 0 : d.titleRunIds;
826
+ Array.isArray(c) && c.length > 0 ? e.push({ id: r, text: s(c) }) : o.contentIds.length > 0 && o.contentIds.every((l) => i.has(l)) && e.push({ id: r, text: s(o.contentIds) });
827
+ for (const l of o.contentIds)
828
+ i.has(l) || n(l);
829
+ };
830
+ return n(a.rootId), e;
831
+ }
832
+ const Y = {
833
+ rootId: "root",
834
+ blocks: [{ id: "root", type: "page", parentId: null, contentIds: [], props: {} }],
835
+ runs: []
836
+ };
837
+ function Wt(a, t) {
838
+ const i = U(a ?? Y), e = U(t ?? Y), s = new Map(i.map((o) => [o.id, o])), n = new Set(e.map((o) => o.id)), r = [];
839
+ for (const o of e) {
840
+ const c = s.get(o.id);
841
+ if (!c) {
842
+ const h = A(o.text) || "&nbsp;";
843
+ r.push(
844
+ `<p class="be-version-diff-block"><span class="be-version-diff-added">${h}</span></p>`
845
+ );
846
+ continue;
847
+ }
848
+ if (c.text === o.text) {
849
+ r.push(`<p class="be-version-diff-block">${A(o.text) || "&nbsp;"}</p>`);
850
+ continue;
851
+ }
852
+ const l = zt(c.text, o.text).map((h) => {
853
+ const u = A(h.text);
854
+ return h.type === "added" ? `<span class="be-version-diff-added">${u}</span>` : h.type === "removed" ? `<span class="be-version-diff-removed">${u}</span>` : u;
855
+ }).join("");
856
+ r.push(`<p class="be-version-diff-block">${l}</p>`);
857
+ }
858
+ for (const o of i)
859
+ if (!n.has(o.id)) {
860
+ const c = A(o.text) || "&nbsp;";
861
+ r.push(
862
+ `<p class="be-version-diff-block"><span class="be-version-diff-removed">${c}</span></p>`
863
+ );
864
+ }
865
+ return r.length === 0 ? '<p class="be-version-diff-empty">No text changes.</p>' : r.join("");
866
+ }
867
+ function Ut(a) {
868
+ const [t, i] = v([]), [e, s] = v(!1), n = Z(() => a ? (s(!1), V(a).then((r) => {
869
+ i(r), s(!0);
870
+ })) : Promise.resolve(), [a]);
871
+ return M(() => {
872
+ if (!a) {
873
+ i([]), s(!0);
874
+ return;
875
+ }
876
+ let r = !1;
877
+ return s(!1), V(a).then((o) => {
878
+ r || (i(o), s(!0));
879
+ }), () => {
880
+ r = !0;
881
+ };
882
+ }, [a]), { versions: t, isLoaded: e, refresh: n };
883
+ }
884
+ const Yt = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
885
+ function Kt(a) {
886
+ const t = new Date(a), i = /* @__PURE__ */ new Date(), e = (n) => new Date(n.getFullYear(), n.getMonth(), n.getDate()).getTime(), s = Math.round((e(i) - e(t)) / (24 * 60 * 60 * 1e3));
887
+ return s === 0 ? "Today" : s === 1 ? "Yesterday" : t.toLocaleDateString(void 0, {
888
+ month: "long",
889
+ day: "numeric",
890
+ year: t.getFullYear() !== i.getFullYear() ? "numeric" : void 0
891
+ });
892
+ }
893
+ function de({ docId: a, idleMs: t, maxVersions: i }) {
894
+ const e = gt(), s = wt(), n = Tt(), { versions: r, isLoaded: o, refresh: c } = Ut(a), [d, l] = v(!1), [h, u] = v(null), [f, T] = v("changes"), w = H(null), g = H(null);
895
+ M(() => {
896
+ if (typeof e.getHistoryLog != "function") return;
897
+ const p = $t({
898
+ store: e,
899
+ docId: a,
900
+ idleMs: t,
901
+ maxVersions: i,
902
+ onSnapshot: c
903
+ });
904
+ return () => p.stop();
905
+ }, [e, a]);
906
+ function _() {
907
+ l(!1), u(null), T("changes");
908
+ }
909
+ M(() => {
910
+ if (!d) return;
911
+ const p = (I) => {
912
+ I.key === "Escape" && _();
913
+ };
914
+ return document.addEventListener("keydown", p), () => document.removeEventListener("keydown", p);
915
+ }, [d]), M(() => {
916
+ var p, I, E;
917
+ if (d) {
918
+ g.current = document.activeElement;
919
+ const P = w.current;
920
+ (p = (P == null ? void 0 : P.querySelector(Yt)) ?? P) == null || p.focus();
921
+ return;
922
+ }
923
+ (E = (I = g.current) == null ? void 0 : I.focus) == null || E.call(I), g.current = null;
924
+ }, [d]);
925
+ const S = r.findIndex((p) => p.id === h), y = S >= 0 ? r[S] : null, C = S >= 0 ? r[S + 1] ?? null : null, N = $(() => {
926
+ if (!y) return "";
927
+ const p = new F(y.doc);
928
+ return X(p, s, n);
929
+ }, [y, s, n]), G = $(() => y ? Wt((C == null ? void 0 : C.doc) ?? null, y.doc) : "", [y, C]);
930
+ function Q() {
931
+ y && window.confirm("Restore this version? This replaces the current document.") && (Bt(e, y.doc), _());
932
+ }
933
+ const x = [];
934
+ for (const p of r) {
935
+ const I = Kt(p.timestamp);
936
+ let E = x[x.length - 1];
937
+ (!E || E.label !== I) && (E = { label: I, versions: [] }, x.push(E)), E.versions.push(p);
938
+ }
939
+ return /* @__PURE__ */ b(D, { children: [
940
+ /* @__PURE__ */ b(
941
+ "button",
942
+ {
943
+ type: "button",
944
+ className: "be-version-history-trigger",
945
+ onClick: () => l(!0),
946
+ title: "Version history",
947
+ children: [
948
+ /* @__PURE__ */ k(Rt, { size: 15 }),
949
+ "Version history"
950
+ ]
951
+ }
952
+ ),
953
+ d && tt(
954
+ /* @__PURE__ */ b(D, { children: [
955
+ /* @__PURE__ */ k("div", { className: "be-version-history-backdrop", onClick: _ }),
956
+ /* @__PURE__ */ b(
957
+ "div",
958
+ {
959
+ ref: w,
960
+ className: "be-version-history-drawer",
961
+ role: "dialog",
962
+ "aria-modal": "true",
963
+ "aria-label": "Version history",
964
+ tabIndex: -1,
965
+ children: [
966
+ /* @__PURE__ */ b("div", { className: "be-version-history-header", children: [
967
+ /* @__PURE__ */ k("span", { children: y ? "Preview" : "Version history" }),
968
+ /* @__PURE__ */ k(
969
+ "button",
970
+ {
971
+ type: "button",
972
+ className: "be-version-history-close",
973
+ onClick: _,
974
+ "aria-label": "Close",
975
+ children: /* @__PURE__ */ k(Et, { size: 14 })
976
+ }
977
+ )
978
+ ] }),
979
+ y ? /* @__PURE__ */ b("div", { className: "be-version-history-preview", children: [
980
+ /* @__PURE__ */ b("div", { className: "be-version-history-preview-meta", children: [
981
+ /* @__PURE__ */ k(j, { authorId: y.authorId, size: 24 }),
982
+ /* @__PURE__ */ b("div", { children: [
983
+ /* @__PURE__ */ k("div", { className: "be-version-history-preview-author", children: y.authorId ?? "Unknown" }),
984
+ /* @__PURE__ */ b("div", { className: "be-version-history-preview-time", children: [
985
+ new Date(y.timestamp).toLocaleString(),
986
+ " · ",
987
+ y.summary
988
+ ] })
989
+ ] })
990
+ ] }),
991
+ /* @__PURE__ */ b(
992
+ "div",
993
+ {
994
+ className: "be-version-history-view-toggle",
995
+ role: "tablist",
996
+ "aria-label": "View mode",
997
+ children: [
998
+ /* @__PURE__ */ k(
999
+ "button",
1000
+ {
1001
+ type: "button",
1002
+ role: "tab",
1003
+ "aria-selected": f === "changes",
1004
+ className: f === "changes" ? "is-active" : "",
1005
+ onClick: () => T("changes"),
1006
+ children: "Changes"
1007
+ }
1008
+ ),
1009
+ /* @__PURE__ */ k(
1010
+ "button",
1011
+ {
1012
+ type: "button",
1013
+ role: "tab",
1014
+ "aria-selected": f === "preview",
1015
+ className: f === "preview" ? "is-active" : "",
1016
+ onClick: () => T("preview"),
1017
+ children: "Preview"
1018
+ }
1019
+ )
1020
+ ]
1021
+ }
1022
+ ),
1023
+ f === "changes" && /* @__PURE__ */ b(D, { children: [
1024
+ /* @__PURE__ */ b("div", { className: "be-version-history-diff-legend", children: [
1025
+ /* @__PURE__ */ k("span", { className: "be-version-diff-added", children: "Added" }),
1026
+ /* @__PURE__ */ k("span", { className: "be-version-diff-removed", children: "Removed" }),
1027
+ !C && /* @__PURE__ */ k("span", { className: "be-version-history-diff-legend-note", children: "First saved version — everything shown is new." })
1028
+ ] }),
1029
+ /* @__PURE__ */ k(
1030
+ "div",
1031
+ {
1032
+ className: "be-version-history-preview-body be-version-history-diff-body",
1033
+ dangerouslySetInnerHTML: { __html: G }
1034
+ }
1035
+ )
1036
+ ] }),
1037
+ f === "preview" && // Static HTML from a throwaway store (see previewHtml above) -- inherently read-only, not a second live editor.
1038
+ /* @__PURE__ */ k(
1039
+ "div",
1040
+ {
1041
+ className: "be-version-history-preview-body",
1042
+ dangerouslySetInnerHTML: { __html: N }
1043
+ }
1044
+ ),
1045
+ /* @__PURE__ */ b("div", { className: "be-version-history-preview-actions", children: [
1046
+ /* @__PURE__ */ k(
1047
+ "button",
1048
+ {
1049
+ type: "button",
1050
+ onClick: () => {
1051
+ u(null), T("changes");
1052
+ },
1053
+ children: "Back"
1054
+ }
1055
+ ),
1056
+ /* @__PURE__ */ b(
1057
+ "button",
1058
+ {
1059
+ type: "button",
1060
+ className: "be-version-history-restore",
1061
+ onClick: Q,
1062
+ children: [
1063
+ /* @__PURE__ */ k(St, { size: 13 }),
1064
+ "Restore this version"
1065
+ ]
1066
+ }
1067
+ )
1068
+ ] })
1069
+ ] }) : /* @__PURE__ */ b("div", { className: "be-version-history-list", children: [
1070
+ !o && /* @__PURE__ */ k("p", { className: "be-version-history-empty", children: "Loading…" }),
1071
+ o && r.length === 0 && /* @__PURE__ */ k("p", { className: "be-version-history-empty", children: "No versions yet — one is saved automatically after you make some edits and pause." }),
1072
+ x.map((p) => /* @__PURE__ */ b("div", { className: "be-version-history-group", children: [
1073
+ /* @__PURE__ */ k("div", { className: "be-version-history-group-label", children: p.label }),
1074
+ p.versions.map((I) => /* @__PURE__ */ b(
1075
+ "button",
1076
+ {
1077
+ type: "button",
1078
+ className: "be-version-history-item",
1079
+ onClick: () => u(I.id),
1080
+ children: [
1081
+ /* @__PURE__ */ k(j, { authorId: I.authorId, size: 24 }),
1082
+ /* @__PURE__ */ b("div", { className: "be-version-history-item-body", children: [
1083
+ /* @__PURE__ */ k("div", { className: "be-version-history-item-author", children: I.authorId ?? "Unknown" }),
1084
+ /* @__PURE__ */ b("div", { className: "be-version-history-item-meta", children: [
1085
+ Mt(I.timestamp),
1086
+ " · ",
1087
+ I.summary
1088
+ ] })
1089
+ ] })
1090
+ ]
1091
+ },
1092
+ I.id
1093
+ ))
1094
+ ] }, p.label))
1095
+ ] })
1096
+ ]
1097
+ }
1098
+ )
1099
+ ] }),
1100
+ document.body
1101
+ )
1102
+ ] });
1103
+ }
1104
+ export {
1105
+ F as E,
1106
+ de as V,
1107
+ X as a,
1108
+ ce as b,
1109
+ $t as c,
1110
+ Wt as d,
1111
+ Vt as e,
1112
+ re as f,
1113
+ oe as g,
1114
+ Bt as h,
1115
+ ne as i,
1116
+ Lt as j,
1117
+ ie as r,
1118
+ Ut as u
1119
+ };
1120
+ //# sourceMappingURL=VersionHistory-Bg6fufP5.js.map