@ai-matrx/office 0.4.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/docs.cjs ADDED
@@ -0,0 +1,657 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+ var _chunkBWMAYLW4cjs = require('./chunk-BWMAYLW4.cjs');
20
+ require('./chunk-2UBGQUXW.cjs');
21
+
22
+ // src/univer/isSnapshotMutation.ts
23
+ var MUTATION = 2;
24
+ var NON_CONTENT_ID_PATTERN = /(scroll|selection|set-selections|viewport|zoom|hover|cursor|focus|activate)/i;
25
+ function isSnapshotMutation(command) {
26
+ if (command.type !== MUTATION) return false;
27
+ if (NON_CONTENT_ID_PATTERN.test(command.id)) return false;
28
+ return true;
29
+ }
30
+
31
+ // src/docs/document-model.ts
32
+ var DOCUMENT_SAVE_DELAY_MS = 2500;
33
+ var viewIds = 0;
34
+ var DocumentModel = (_class = class {
35
+
36
+
37
+
38
+ __init() {this.views = /* @__PURE__ */ new Map()}
39
+ __init2() {this.lastEditedView = null}
40
+ /** The document as the last view left it, or as last loaded / saved. */
41
+ __init3() {this.stored = null}
42
+ __init4() {this.loading = null}
43
+ __init5() {this.relaying = false}
44
+ /** Snapshot rows this tab already holds (loaded or written) — never re-applied. */
45
+ __init6() {this.knownSnapshots = /* @__PURE__ */ new Set()}
46
+ __init7() {this.collabListeners = /* @__PURE__ */ new Set()}
47
+ __init8() {this.collab = null}
48
+ __init9() {this.collabStarting = null}
49
+ __init10() {this.awareness = null}
50
+ __init11() {this.awarenessListeners = /* @__PURE__ */ new Set()}
51
+ __init12() {this.realtimeClose = null}
52
+ __init13() {this.pageClose = null}
53
+ /** The last view's editor, alive and off the page until a view takes it back. */
54
+ __init14() {this.parked = null}
55
+ constructor(handle, deps) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this);_class.prototype.__init8.call(this);_class.prototype.__init9.call(this);_class.prototype.__init10.call(this);_class.prototype.__init11.call(this);_class.prototype.__init12.call(this);_class.prototype.__init13.call(this);_class.prototype.__init14.call(this);_class.prototype.__init15.call(this);_class.prototype.__init16.call(this);_class.prototype.__init17.call(this);_class.prototype.__init18.call(this);
56
+ this.documentId = handle.id;
57
+ this.handle = handle;
58
+ this.deps = deps;
59
+ }
60
+ // ── body ────────────────────────────────────────────────────────────────
61
+ /** The newest state of the body this tab holds, without a network read. */
62
+ latest() {
63
+ const preferred = this.lastEditedView !== null ? this.views.get(this.lastEditedView) : void 0;
64
+ for (const view of preferred ? [preferred, ...this.views.values()] : this.views.values()) {
65
+ try {
66
+ const snapshot = view.port.snapshot();
67
+ if (snapshot) return snapshot;
68
+ } catch (error) {
69
+ console.warn(`[document-model] a view of ${this.documentId} could not report its document`, error);
70
+ }
71
+ }
72
+ return this.stored;
73
+ }
74
+ /**
75
+ * What a NEW view should boot from: the model's latest state, or (only when
76
+ * this tab holds none) the newest server snapshot. Null = an empty document.
77
+ * Re-read `latest()` synchronously right before mounting — another view may
78
+ * have typed while the server read was in flight.
79
+ */
80
+ async openingSnapshot(createEmpty) {
81
+ const held = this.latest();
82
+ if (held) return held;
83
+ if (!this.loading) {
84
+ this.loading = this.deps.loadLatest(this.documentId).then(
85
+ (row) => {
86
+ if (row) {
87
+ this.knownSnapshots.add(row.id);
88
+ this.savedFingerprint = contentFingerprint(row.snapshot);
89
+ }
90
+ if (!this.stored) this.stored = row ? row.snapshot : createEmpty();
91
+ return _nullishCoalesce(this.latest(), () => ( this.stored));
92
+ },
93
+ (error) => {
94
+ this.loading = null;
95
+ throw error;
96
+ }
97
+ );
98
+ }
99
+ return await _asyncNullishCoalesce(await this.loading, async () => ( createEmpty()));
100
+ }
101
+ /** A view's Univer instance is ready: it shares the document from now on. */
102
+ attachView(port) {
103
+ const id = ++viewIds;
104
+ const subscription = port.commandService.onMutationExecutedForCollab(
105
+ (info, options) => this.onViewMutation(id, info, options)
106
+ );
107
+ this.views.set(id, { id, port, dispose: () => subscription.dispose() });
108
+ if (!this.handle.hasPending()) {
109
+ try {
110
+ const mounted = port.snapshot();
111
+ if (mounted) this.savedFingerprint = contentFingerprint(mounted);
112
+ } catch (error) {
113
+ console.warn(`[document-model] could not read ${this.documentId}'s mounted document`, error);
114
+ }
115
+ }
116
+ let detached = false;
117
+ return () => {
118
+ if (detached) return;
119
+ detached = true;
120
+ const view = this.views.get(id);
121
+ if (!view) return;
122
+ if (this.views.size === 1) {
123
+ try {
124
+ const snapshot = port.snapshot();
125
+ if (snapshot) this.stored = snapshot;
126
+ } catch (error) {
127
+ console.warn(`[document-model] could not keep ${this.documentId}'s last view's document`, error);
128
+ }
129
+ }
130
+ view.dispose();
131
+ this.views.delete(id);
132
+ if (this.lastEditedView === id) this.lastEditedView = null;
133
+ };
134
+ }
135
+ /**
136
+ * The last view of the document is leaving: keep its editor (and Univer's
137
+ * undo history inside it) for the next view. Call AFTER the view's
138
+ * `attachView` detach. Refused — the caller disposes its editor — while
139
+ * another view is still attached (it carries the document) or one is
140
+ * already kept.
141
+ */
142
+ parkEditor(editor) {
143
+ if (this.closed || this.views.size > 0 || this.parked) return false;
144
+ this.parked = editor;
145
+ return true;
146
+ }
147
+ /** The kept editor, handed to the view that mounts next (null when none is kept). */
148
+ takeParkedEditor() {
149
+ const editor = this.parked;
150
+ this.parked = null;
151
+ return editor;
152
+ }
153
+ onViewMutation(viewId, info, options) {
154
+ if (this.relaying || _optionalChain([options, 'optionalAccess', _ => _.fromCollab]) || _optionalChain([options, 'optionalAccess', _2 => _2.fromChangeset])) return;
155
+ const view = this.views.get(viewId);
156
+ if (!view || !view.port.editable()) return;
157
+ this.lastEditedView = viewId;
158
+ this.relay(info, viewId);
159
+ for (const listener of this.collabListeners) listener(info);
160
+ if (isContentMutation(info)) this.markDirty();
161
+ }
162
+ /** The unit id a view's document has (what a replayed mutation must be addressed to). */
163
+ unitIdOf(view) {
164
+ try {
165
+ return _nullishCoalesce(_nullishCoalesce(_optionalChain([view, 'access', _3 => _3.port, 'access', _4 => _4.unitId, 'optionalCall', _5 => _5()]), () => ( _optionalChain([view, 'access', _6 => _6.port, 'access', _7 => _7.snapshot, 'call', _8 => _8(), 'optionalAccess', _9 => _9.id]))), () => ( null));
166
+ } catch (e3) {
167
+ return null;
168
+ }
169
+ }
170
+ /** Replay one mutation into every view except `exceptViewId`, addressed to each view's own unit. */
171
+ relay(info, exceptViewId) {
172
+ if (this.views.size === 0 || this.views.size === 1 && exceptViewId !== null) return;
173
+ this.relaying = true;
174
+ let failure = null;
175
+ try {
176
+ for (const view of this.views.values()) {
177
+ if (view.id === exceptViewId) continue;
178
+ try {
179
+ const params = info.params;
180
+ const unitId = this.unitIdOf(view);
181
+ const addressed = unitId && params && typeof params === "object" && !Array.isArray(params) && typeof params.unitId === "string" && params.unitId !== unitId ? { ...params, unitId } : info.params;
182
+ view.port.commandService.syncExecuteCommand(info.id, addressed, { onlyLocal: true, fromCollab: true });
183
+ } catch (error) {
184
+ failure ??= error;
185
+ console.error(
186
+ `[document-model] a second view of ${this.documentId} could not apply "${info.id}" \u2014 it may show an older document until it reopens`,
187
+ error
188
+ );
189
+ }
190
+ }
191
+ } finally {
192
+ this.relaying = false;
193
+ }
194
+ if (failure !== null) this.applyFailed(info.id, failure);
195
+ }
196
+ /** A view could not follow an edit: say so, then show the saved version (unless this tab has unsaved edits of its own). */
197
+ applyFailed(mutationId, error) {
198
+ try {
199
+ _optionalChain([this, 'access', _10 => _10.deps, 'access', _11 => _11.onApplyFailed, 'optionalCall', _12 => _12(this.documentId, mutationId, error)]);
200
+ } catch (notifyError) {
201
+ console.warn(`[document-model] the notice for ${this.documentId}'s failed apply also failed`, notifyError);
202
+ }
203
+ if (this.hasUnsaved()) return;
204
+ void this.deps.loadLatest(this.documentId).then((row) => {
205
+ if (row && !this.closed) this.show(row);
206
+ }).catch((e2) => console.warn(`[document-model] could not re-sync ${this.documentId} after a failed apply`, e2));
207
+ }
208
+ // ── save (run by the record's one coalesced save — documentModels.ts) ─
209
+ markDirty() {
210
+ this.handle.touch();
211
+ }
212
+ __init15() {this.saving = false}
213
+ /**
214
+ * The content of the version the server holds (loaded or last written). A
215
+ * save of the same content is no save: attaching, detaching, waking or a
216
+ * flush of an unchanged document never writes a snapshot row.
217
+ */
218
+ __init16() {this.savedFingerprint = null}
219
+ /**
220
+ * Write the body as it is now. Returns false when this tab is a collab peer
221
+ * that does not write (the elected host writes the canonical snapshot; a
222
+ * peer's edits reach it through the room). An explicit Save always writes.
223
+ */
224
+ async write(reason) {
225
+ const snapshot = this.latest();
226
+ if (!snapshot) throw new Error("There is no open document to save.");
227
+ if (this.collab && !this.collab.isHost() && reason !== "manual") return false;
228
+ const fingerprint = contentFingerprint(snapshot);
229
+ if (reason !== "manual" && fingerprint === this.savedFingerprint) return false;
230
+ const origin = reason === "manual" ? "manual" : "autosave";
231
+ this.saving = true;
232
+ try {
233
+ const { id } = await this.deps.save({ documentId: this.documentId, snapshot, origin });
234
+ this.knownSnapshots.add(id);
235
+ this.savedFingerprint = fingerprint;
236
+ } finally {
237
+ this.saving = false;
238
+ }
239
+ if (this.views.size === 0) this.stored = snapshot;
240
+ _optionalChain([this, 'access', _13 => _13.deps, 'access', _14 => _14.onSaved, 'optionalCall', _15 => _15(this.documentId, origin)]);
241
+ return true;
242
+ }
243
+ hasUnsaved() {
244
+ return this.handle.hasPending();
245
+ }
246
+ hasViews() {
247
+ return this.views.size > 0;
248
+ }
249
+ // ── realtime (snapshot inserts) ─────────────────────────────────────────
250
+ /** Opened once per document by the first view that asks; idempotent. */
251
+ connectRealtime(open) {
252
+ if (this.realtimeClose) return;
253
+ this.realtimeClose = open((snapshotId) => void this.onRemoteSnapshot(snapshotId));
254
+ }
255
+ /**
256
+ * A snapshot was committed (here or elsewhere): show a foreign one in every
257
+ * view. Over unsaved edits it is a CONFLICT — the working copy holds it and
258
+ * no save runs (one would overwrite the collaborator's snapshot) until the
259
+ * person chooses; `overUnsaved` is "Take theirs" (`./documentModels.ts`).
260
+ */
261
+ async onRemoteSnapshot(snapshotId, { overUnsaved = false } = {}) {
262
+ if (this.collab) return;
263
+ if (this.knownSnapshots.has(snapshotId)) return;
264
+ if (this.saving) return;
265
+ if (this.hasUnsaved() && !overUnsaved) {
266
+ this.handle.conflict({ ref: snapshotId });
267
+ return;
268
+ }
269
+ const row = await this.deps.loadLatest(this.documentId);
270
+ if (!row || this.hasUnsaved() && !overUnsaved || this.knownSnapshots.has(row.id)) return;
271
+ this.show(row);
272
+ }
273
+ /**
274
+ * THE content swap (see @ai-matrx/office/univer openEditorContent): a SAVED snapshot
275
+ * replaces the document in every view and the kept editor. It is the saved
276
+ * version from now on, so the pending save compares equal and writes
277
+ * nothing (it would put the old content back over the new). In a collab
278
+ * room the peers reload it and the shared log is emptied.
279
+ */
280
+ replaceContent(row) {
281
+ this.show(row);
282
+ _optionalChain([this, 'access', _16 => _16.collab, 'optionalAccess', _17 => _17.announceReplaced, 'call', _18 => _18(row.id)]);
283
+ }
284
+ /** A peer replaced the document: show the newest save (once). */
285
+ async onPeerReplaced() {
286
+ const row = await this.deps.loadLatest(this.documentId);
287
+ if (!row || this.knownSnapshots.has(row.id)) return;
288
+ this.show(row);
289
+ }
290
+ show(row) {
291
+ this.knownSnapshots.add(row.id);
292
+ this.savedFingerprint = contentFingerprint(row.snapshot);
293
+ this.stored = row.snapshot;
294
+ this.relaying = true;
295
+ try {
296
+ for (const view of this.views.values()) view.port.remount(row.snapshot);
297
+ _optionalChain([this, 'access', _19 => _19.parked, 'optionalAccess', _20 => _20.remount, 'call', _21 => _21(row.snapshot)]);
298
+ } finally {
299
+ this.relaying = false;
300
+ }
301
+ }
302
+ // ── collab (one room per document) ──────────────────────────────────────
303
+ /**
304
+ * The model's command stream as ONE command service — what the collab room
305
+ * binds to. Local edits from any view go out once; a peer's edit is applied
306
+ * to every view.
307
+ */
308
+ __init17() {this.virtualCommandService = {
309
+ onMutationExecutedForCollab: (listener) => {
310
+ this.collabListeners.add(listener);
311
+ return { dispose: () => this.collabListeners.delete(listener) };
312
+ },
313
+ syncExecuteCommand: (id, params) => {
314
+ const info = { id, type: 2, params };
315
+ this.relay(info, null);
316
+ if (isContentMutation(info)) this.markDirty();
317
+ return true;
318
+ }
319
+ }}
320
+ /** Join the document's collab room once (the first view that asks). */
321
+ startCollab(factory) {
322
+ if (this.collab) return Promise.resolve();
323
+ if (!this.collabStarting) {
324
+ this.collabStarting = factory(
325
+ this.virtualCommandService,
326
+ (awareness) => {
327
+ this.awareness = awareness;
328
+ for (const listener of Array.from(this.awarenessListeners)) listener();
329
+ },
330
+ () => {
331
+ void this.onPeerReplaced().catch(
332
+ (error) => console.warn(`[document-model] could not load ${this.documentId}'s replaced version`, error)
333
+ );
334
+ }
335
+ ).then((handle) => {
336
+ if (!handle) return;
337
+ if (this.views.size === 0) {
338
+ handle.stop();
339
+ return;
340
+ }
341
+ this.collab = handle;
342
+ }).finally(() => {
343
+ this.collabStarting = null;
344
+ });
345
+ }
346
+ return this.collabStarting;
347
+ }
348
+ isCollabHost() {
349
+ return this.collab ? this.collab.isHost() : true;
350
+ }
351
+ getAwareness() {
352
+ return this.awareness;
353
+ }
354
+ subscribeAwareness(listener) {
355
+ this.awarenessListeners.add(listener);
356
+ return () => this.awarenessListeners.delete(listener);
357
+ }
358
+ // ── lifecycle (driven by the record's session) ──────────────────────
359
+ /** A view arrived on a document with none. */
360
+ wake() {
361
+ if (!this.pageClose && this.deps.listenToPage) {
362
+ this.pageClose = this.deps.listenToPage({
363
+ flush: () => void this.handle.flush(),
364
+ hasUnsaved: () => this.hasUnsaved()
365
+ });
366
+ }
367
+ }
368
+ /**
369
+ * The last view left: leave the collab room (presence must not outlive the
370
+ * person). The snapshot channel stays open while the session is kept alive,
371
+ * so a collaborator's save still reaches the body a returning view boots from.
372
+ */
373
+ rest() {
374
+ _optionalChain([this, 'access', _22 => _22.collab, 'optionalAccess', _23 => _23.stop, 'call', _24 => _24()]);
375
+ this.collab = null;
376
+ this.awareness = null;
377
+ }
378
+ __init18() {this.closed = false}
379
+ close() {
380
+ this.closed = true;
381
+ const parked = this.parked;
382
+ this.parked = null;
383
+ if (parked) {
384
+ try {
385
+ parked.dispose();
386
+ } catch (error) {
387
+ console.warn(`[document-model] could not dispose ${this.documentId}'s kept editor`, error);
388
+ }
389
+ }
390
+ _optionalChain([this, 'access', _25 => _25.realtimeClose, 'optionalCall', _26 => _26()]);
391
+ this.realtimeClose = null;
392
+ _optionalChain([this, 'access', _27 => _27.pageClose, 'optionalCall', _28 => _28()]);
393
+ this.pageClose = null;
394
+ }
395
+ }, _class);
396
+ function contentFingerprint(snapshot) {
397
+ const { id: _id, rev: _rev, ...content } = snapshot;
398
+ return JSON.stringify(content);
399
+ }
400
+ function isContentMutation(info) {
401
+ return isSnapshotMutation({ id: info.id, type: info.type, params: info.params });
402
+ }
403
+
404
+ // src/docs/document-write-validation.ts
405
+ var DOCUMENT_NAME_MAX_LENGTH = 255;
406
+ var DOCUMENT_DESCRIPTION_MAX_LENGTH = 2e3;
407
+ function describeValue(value) {
408
+ if (value === null) return "null";
409
+ if (Array.isArray(value)) return "an array";
410
+ if (typeof value === "object") return "an object";
411
+ return `a ${typeof value}`;
412
+ }
413
+ function validateDocumentName(value) {
414
+ if (typeof value !== "string") {
415
+ throw new Error(
416
+ `document_name must be plain text, not JSON and not JSON-encoded \u2014 received ${describeValue(
417
+ value
418
+ )}. Send only the title itself (for example: Q3 Revenue Review) with no surrounding braces, brackets, quotes or escape sequences.`
419
+ );
420
+ }
421
+ const trimmed = value.trim();
422
+ if (trimmed.length === 0) {
423
+ throw new Error(
424
+ "document_name cannot be empty or whitespace \u2014 a document must keep a title. Send the title text you want, or leave the current one alone."
425
+ );
426
+ }
427
+ if (/[\r\n]/.test(trimmed)) {
428
+ throw new Error(
429
+ "document_name must be a SINGLE line \u2014 it renders in the page header, and line breaks are not a title. Send one line of plain text (this is a title, not the document body)."
430
+ );
431
+ }
432
+ if (trimmed.length > DOCUMENT_NAME_MAX_LENGTH) {
433
+ throw new Error(
434
+ `document_name must be ${DOCUMENT_NAME_MAX_LENGTH} characters or fewer (received ${trimmed.length}). Shorten it to a title; the document body is not stored in this field.`
435
+ );
436
+ }
437
+ return trimmed;
438
+ }
439
+ function validateDocumentDescription(value) {
440
+ if (typeof value !== "string") {
441
+ throw new Error(
442
+ `document_description must be plain text, not JSON and not JSON-encoded \u2014 received ${describeValue(
443
+ value
444
+ )}. Send only the description prose itself, with no surrounding braces, brackets, quotes or escape sequences.`
445
+ );
446
+ }
447
+ const trimmed = value.trim();
448
+ if (trimmed.length === 0) return null;
449
+ if (trimmed.length > DOCUMENT_DESCRIPTION_MAX_LENGTH) {
450
+ throw new Error(
451
+ `document_description must be ${DOCUMENT_DESCRIPTION_MAX_LENGTH} characters or fewer (received ${trimmed.length}). This is the short blurb shown in the documents library, not the document body.`
452
+ );
453
+ }
454
+ return trimmed;
455
+ }
456
+
457
+ // src/docs/fit-zoom-ratio.ts
458
+ var MIN_FIT_ZOOM = 0.45;
459
+ function fitZoomRatio(containerWidth, pageWidth, gutter = 8) {
460
+ if (!(containerWidth > 0) || !(pageWidth > 0)) return 1;
461
+ const ratio = (containerWidth - gutter * 2) / pageWidth;
462
+ return Math.min(1, Math.max(MIN_FIT_ZOOM, Math.round(ratio * 100) / 100));
463
+ }
464
+
465
+ // src/docs/documents-hub-display.ts
466
+ var SOURCE_LABELS = {
467
+ created: "Created",
468
+ imported_docx: "Imported DOCX",
469
+ imported_md: "Imported Markdown",
470
+ imported_txt: "Imported Text",
471
+ linked_gdoc: "Imported from Google Docs"
472
+ };
473
+ function documentSourceLabel(source) {
474
+ return _nullishCoalesce(SOURCE_LABELS[source], () => ( source));
475
+ }
476
+
477
+ // src/docs/doc-symbols.ts
478
+ var e = (rows) => rows.split("|").map((r) => r.trim()).filter(Boolean).map((r) => {
479
+ const [char, ...name] = r.split(" ");
480
+ return { char, name: name.join(" ") };
481
+ });
482
+ var EMOJI_GROUPS = [
483
+ { id: "faces", label: "Faces", items: e("\u{1F600} grinning|\u{1F603} smile|\u{1F604} happy|\u{1F601} beaming|\u{1F606} laughing|\u{1F605} sweat smile|\u{1F602} tears of joy|\u{1F923} rolling|\u{1F642} slight smile|\u{1F643} upside down|\u{1F609} wink|\u{1F60A} blush|\u{1F607} angel|\u{1F970} love|\u{1F60D} heart eyes|\u{1F929} star struck|\u{1F618} kiss|\u{1F60B} yum|\u{1F60E} cool|\u{1F913} nerd|\u{1F914} thinking|\u{1F928} skeptical|\u{1F610} neutral|\u{1F644} eye roll|\u{1F62C} grimace|\u{1F634} sleeping|\u{1F622} cry|\u{1F62D} sobbing|\u{1F624} huff|\u{1F621} angry|\u{1F92F} mind blown|\u{1F631} scream|\u{1F973} party|\u{1F917} hug|\u{1FAE1} salute|\u{1F62E} surprised|\u{1F633} flushed|\u{1F97A} pleading|\u{1F637} mask|\u{1F912} sick") },
484
+ { id: "people", label: "Gestures", items: e("\u{1F44D} thumbs up|\u{1F44E} thumbs down|\u{1F44F} clap|\u{1F64C} raised hands|\u{1F64F} pray thanks|\u{1F91D} handshake|\u{1F4AA} strong flex|\u{1F44C} ok|\u270C victory peace|\u{1F91E} fingers crossed|\u{1F44B} wave|\u270B raised hand|\u{1F446} point up|\u{1F449} point right|\u{1F448} point left|\u{1F447} point down|\u270D writing|\u{1F937} shrug|\u{1F64B} raise hand|\u{1F440} eyes|\u{1F9E0} brain|\u2764 heart|\u{1F494} broken heart|\u{1F4AF} hundred") },
485
+ { id: "objects", label: "Objects", items: e("\u{1F4A1} idea light bulb|\u{1F4CC} pin|\u{1F4CE} paperclip|\u{1F4DD} memo note|\u{1F4C5} calendar|\u{1F4C8} chart up|\u{1F4C9} chart down|\u{1F4CA} bar chart|\u{1F4C1} folder|\u{1F4C4} page|\u{1F4E7} email|\u{1F4DE} phone|\u{1F4BB} laptop|\u{1F4F1} mobile|\u{1F512} lock|\u{1F511} key|\u{1F50D} search|\u{1F514} bell|\u23F0 alarm clock|\u231B hourglass|\u{1F3AF} target|\u{1F3C6} trophy|\u{1F381} gift|\u{1F4E6} package|\u{1F6E0} tools|\u2699 gear|\u{1F4B0} money bag|\u{1F4B5} dollar|\u{1F9FE} receipt|\u{1F4DA} books") },
486
+ { id: "symbols", label: "Marks", items: e("\u2705 check mark|\u274C cross|\u2B50 star|\u{1F31F} glowing star|\u2728 sparkles|\u{1F525} fire|\u26A1 lightning|\u{1F680} rocket|\u{1F389} party popper|\u{1F38A} confetti|\u26A0 warning|\u2753 question|\u2757 exclamation|\u2795 plus|\u2796 minus|\u{1F534} red circle|\u{1F7E0} orange circle|\u{1F7E1} yellow circle|\u{1F7E2} green circle|\u{1F535} blue circle|\u{1F7E3} purple circle|\u2B06 up|\u2B07 down|\u27A1 right|\u2B05 left|\u{1F501} repeat|\u2714 heavy check") },
487
+ { id: "nature", label: "Nature and food", items: e("\u{1F31E} sun|\u{1F319} moon|\u2601 cloud|\u{1F327} rain|\u2744 snowflake|\u{1F308} rainbow|\u{1F331} seedling|\u{1F333} tree|\u{1F338} blossom|\u{1F30D} earth|\u{1F436} dog|\u{1F431} cat|\u{1F98A} fox|\u{1F41D} bee|\u{1F98B} butterfly|\u{1F34E} apple|\u{1F355} pizza|\u{1F354} burger|\u2615 coffee|\u{1F37A} beer|\u{1F377} wine|\u{1F382} cake|\u{1F36A} cookie|\u{1F951} avocado") }
488
+ ];
489
+ var s = (chars, names) => Array.from(chars).map((char, i) => ({ char, name: _nullishCoalesce(names[i], () => ( char)) }));
490
+ var SPECIAL_GROUPS = [
491
+ { id: "punctuation", label: "Punctuation", items: s("\xA9\xAE\u2122\xB0\xA7\xB6\u2020\u2021\u2022\u2026\u2030\u2116\u2018\u2019\u201C\u201D\xAB\xBB\u2013\u2014\xA1\xBF\xB7", ["copyright", "registered", "trademark", "degree", "section", "pilcrow", "dagger", "double dagger", "bullet", "ellipsis", "per mille", "numero", "left single quote", "right single quote", "left double quote", "right double quote", "left guillemet", "right guillemet", "en dash", "em dash", "inverted exclamation", "inverted question", "middle dot"]) },
492
+ { id: "arrows", label: "Arrows", items: s("\u2190\u2191\u2192\u2193\u2194\u2195\u21D0\u21D1\u21D2\u21D3\u21D4\u21A9\u21AA\u279C\u27A4\u2196\u2197\u2198\u2199\u21BA\u21BB", ["left arrow", "up arrow", "right arrow", "down arrow", "left right arrow", "up down arrow", "left double arrow", "up double arrow", "right double arrow", "down double arrow", "left right double arrow", "return arrow", "hook arrow", "heavy arrow", "arrowhead", "north west arrow", "north east arrow", "south east arrow", "south west arrow", "anticlockwise arrow", "clockwise arrow"]) },
493
+ { id: "math", label: "Math", items: s("\xB1\xD7\xF7\u2248\u2260\u2264\u2265\u221E\u221A\u2211\u220F\u222B\u2202\u2206\u2207\u2208\u2209\u222A\u2229\u2282\u2283\u2227\u2228\xAC\u2200\u2203\u2205\u221D\u2220\xBC\xBD\xBE\u2153\u2154\xB2\xB3", ["plus minus", "times", "divide", "approximately", "not equal", "less or equal", "greater or equal", "infinity", "square root", "sum", "product", "integral", "partial", "increment", "nabla", "element of", "not element", "union", "intersection", "subset", "superset", "and", "or", "not", "for all", "exists", "empty set", "proportional", "angle", "one quarter", "one half", "three quarters", "one third", "two thirds", "squared", "cubed"]) },
494
+ { id: "currency", label: "Currency", items: s("$\u20AC\xA3\xA5\xA2\u20B9\u20A9\u20BD\u20BA\u20BF\u0E3F\u20AB\u20AA\u20B4\u20A6\u20B1", ["dollar", "euro", "pound", "yen", "cent", "rupee", "won", "ruble", "lira", "bitcoin", "baht", "dong", "shekel", "hryvnia", "naira", "peso"]) },
495
+ { id: "greek", label: "Greek", items: s("\u03B1\u03B2\u03B3\u03B4\u03B5\u03B6\u03B7\u03B8\u03B9\u03BA\u03BB\u03BC\u03BD\u03BE\u03BF\u03C0\u03C1\u03C3\u03C4\u03C5\u03C6\u03C7\u03C8\u03C9\u03A9\u03A3\u0394\u03A0\u03A6\u03A8\u039B\u0398", ["alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", "mu", "nu", "xi", "omicron", "pi", "rho", "sigma", "tau", "upsilon", "phi", "chi", "psi", "omega", "capital omega", "capital sigma", "capital delta", "capital pi", "capital phi", "capital psi", "capital lambda", "capital theta"]) },
496
+ { id: "latin", label: "Accented letters", items: s("\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF1\xF2\xF3\xF4\xF5\xF6\xF8\u0153\xF9\xFA\xFB\xFC\xFD\xFF\xDF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD1\xD2\xD3\xD4\xD5\xD6\xD8\u0152\xD9\xDA\xDB\xDC\xDD\u0178", []) },
497
+ { id: "shapes", label: "Shapes", items: s("\u25A0\u25A1\u25B2\u25B3\u25BC\u25BD\u25C6\u25C7\u25CF\u25CB\u2605\u2606\u2660\u2663\u2665\u2666\u266A\u266B\u2611\u2612", ["black square", "white square", "black up triangle", "white up triangle", "black down triangle", "white down triangle", "black diamond", "white diamond", "black circle", "white circle", "black star", "white star", "spade", "club", "heart suit", "diamond suit", "eighth note", "beamed notes", "checked box", "crossed box"]) }
498
+ ];
499
+ function searchSymbols(groups, query) {
500
+ const q = query.trim().toLowerCase();
501
+ if (!q) return [];
502
+ const seen = /* @__PURE__ */ new Set();
503
+ const out = [];
504
+ for (const g of groups) for (const it of g.items) if ((it.char === q || it.name.toLowerCase().includes(q)) && !seen.has(it.char)) seen.add(it.char), out.push(it);
505
+ return out;
506
+ }
507
+
508
+ // src/docs/document-body-text.ts
509
+ var DOCUMENT_BODY_MAX_LENGTH = 2e5;
510
+ var PARAGRAPH = "\r";
511
+ var SECTION = "\n";
512
+ function bodyTextOf(dataStream) {
513
+ let text = "";
514
+ const offsets = [];
515
+ for (let i = 0; i < dataStream.length; i++) {
516
+ const ch = dataStream[i];
517
+ if (ch === PARAGRAPH) {
518
+ text += "\n";
519
+ offsets.push(i);
520
+ continue;
521
+ }
522
+ if (ch === SECTION) continue;
523
+ if (ch !== " " && ch.charCodeAt(0) < 32) continue;
524
+ text += ch;
525
+ offsets.push(i);
526
+ }
527
+ if (text.endsWith("\n")) {
528
+ const end = offsets[offsets.length - 1];
529
+ text = text.slice(0, -1);
530
+ offsets.pop();
531
+ offsets.push(end);
532
+ } else {
533
+ offsets.push(dataStream.length);
534
+ }
535
+ return { text, offsets };
536
+ }
537
+ function planBodyEdit(current, next) {
538
+ const a = current.text;
539
+ const b = next;
540
+ if (a === b) return null;
541
+ let prefix = 0;
542
+ const max = Math.min(a.length, b.length);
543
+ while (prefix < max && a[prefix] === b[prefix]) prefix++;
544
+ let suffix = 0;
545
+ while (suffix < max - prefix && a[a.length - 1 - suffix] === b[b.length - 1 - suffix])
546
+ suffix++;
547
+ const removeTo = a.length - suffix;
548
+ return {
549
+ start: current.offsets[prefix],
550
+ end: current.offsets[removeTo],
551
+ insert: b.slice(prefix, b.length - suffix)
552
+ };
553
+ }
554
+ function writeBodyText(port, next) {
555
+ const current = bodyTextOf(port.getDataStream());
556
+ const edit = planBodyEdit(current, next);
557
+ if (!edit) return { changed: false, removed: 0, inserted: 0 };
558
+ if (edit.end > edit.start && !port.deleteRange(edit.start, edit.end)) {
559
+ throw new Error(
560
+ "The editor refused to remove the old text (the document may be read-only right now). Nothing was changed."
561
+ );
562
+ }
563
+ if (edit.insert.length > 0 && !port.insertText(edit.start, edit.insert)) {
564
+ throw new Error(
565
+ edit.end > edit.start ? "The editor removed the old text but refused the new text. Press Undo in the document to bring the old text back, then try again." : "The editor refused the new text (the document may be read-only right now). Nothing was changed."
566
+ );
567
+ }
568
+ return {
569
+ changed: true,
570
+ removed: edit.end - edit.start,
571
+ inserted: edit.insert.length
572
+ };
573
+ }
574
+ function describeValue2(value) {
575
+ if (value === null) return "null";
576
+ if (Array.isArray(value)) return "an array";
577
+ if (typeof value === "object") return "an object";
578
+ return `a ${typeof value}`;
579
+ }
580
+ function validateDocumentBody(value) {
581
+ if (typeof value !== "string") {
582
+ throw new Error(
583
+ `document_body must be the document's whole text as a plain string (or an anchored edit {command, old_str, new_str}) \u2014 received ${describeValue2(
584
+ value
585
+ )}. Read document_body_text, change it, and send the full new text.`
586
+ );
587
+ }
588
+ if (value.length > DOCUMENT_BODY_MAX_LENGTH) {
589
+ throw new Error(
590
+ `document_body must be ${DOCUMENT_BODY_MAX_LENGTH} characters or fewer (received ${value.length}). Nothing was changed.`
591
+ );
592
+ }
593
+ return value.replace(/\r\n?/g, "\n");
594
+ }
595
+ function documentBodyWriteHandler(deps) {
596
+ return {
597
+ validate: (value) => {
598
+ validateDocumentBody(value);
599
+ },
600
+ apply: (value) => {
601
+ const next = validateDocumentBody(value);
602
+ deps.assertWritable("its text cannot be changed");
603
+ const port = deps.getPort();
604
+ if (!port) {
605
+ throw new Error(
606
+ "The document editor has not finished opening yet. Nothing was changed; try again in a moment."
607
+ );
608
+ }
609
+ const result = writeBodyText(port, next);
610
+ if (!result.changed) {
611
+ return { summary: "The document already has exactly this text; nothing changed." };
612
+ }
613
+ return {
614
+ summary: `Changed the document's text in the editor (removed ${result.removed} characters, inserted ${result.inserted}); the editor saves it within a few seconds, and Undo in the document reverses it.`,
615
+ data: { removed: result.removed, inserted: result.inserted }
616
+ };
617
+ }
618
+ };
619
+ }
620
+
621
+
622
+
623
+
624
+
625
+
626
+
627
+
628
+
629
+
630
+
631
+
632
+
633
+
634
+
635
+
636
+
637
+
638
+
639
+
640
+
641
+
642
+
643
+
644
+
645
+
646
+
647
+
648
+
649
+
650
+
651
+
652
+
653
+
654
+
655
+
656
+ exports.DOCUMENT_BODY_MAX_LENGTH = DOCUMENT_BODY_MAX_LENGTH; exports.DOCUMENT_DESCRIPTION_MAX_LENGTH = DOCUMENT_DESCRIPTION_MAX_LENGTH; exports.DOCUMENT_NAME_MAX_LENGTH = DOCUMENT_NAME_MAX_LENGTH; exports.DOCUMENT_SAVE_DELAY_MS = DOCUMENT_SAVE_DELAY_MS; exports.DocumentModel = DocumentModel; exports.EMOJI_GROUPS = EMOJI_GROUPS; exports.MIN_FIT_ZOOM = MIN_FIT_ZOOM; exports.SPECIAL_GROUPS = SPECIAL_GROUPS; exports.bodyTextOf = bodyTextOf; exports.configureDocsHost = _chunkBWMAYLW4cjs.configureDocsHost; exports.copyDocument = _chunkBWMAYLW4cjs.copyDocument; exports.createDocument = _chunkBWMAYLW4cjs.createDocument; exports.deleteDocument = _chunkBWMAYLW4cjs.deleteDocument; exports.discardFailedDocument = _chunkBWMAYLW4cjs.discardFailedDocument; exports.documentBodyWriteHandler = documentBodyWriteHandler; exports.documentSourceLabel = documentSourceLabel; exports.fitZoomRatio = fitZoomRatio; exports.getDocsHost = _chunkBWMAYLW4cjs.getDocsHost; exports.getDocument = _chunkBWMAYLW4cjs.getDocument; exports.getLatestDocumentSnapshot = _chunkBWMAYLW4cjs.getLatestDocumentSnapshot; exports.listAccessibleDocuments = _chunkBWMAYLW4cjs.listAccessibleDocuments; exports.listDocumentSnapshots = _chunkBWMAYLW4cjs.listDocumentSnapshots; exports.listTrashedDocuments = _chunkBWMAYLW4cjs.listTrashedDocuments; exports.moveDocument = _chunkBWMAYLW4cjs.moveDocument; exports.planBodyEdit = planBodyEdit; exports.renameDocument = _chunkBWMAYLW4cjs.renameDocument; exports.restoreDocument = _chunkBWMAYLW4cjs.restoreDocument; exports.sanitizeUniverDocSnapshot = _chunkBWMAYLW4cjs.sanitizeUniverDocSnapshot; exports.saveDocumentSnapshot = _chunkBWMAYLW4cjs.saveDocumentSnapshot; exports.searchSymbols = searchSymbols; exports.updateDocumentDescription = _chunkBWMAYLW4cjs.updateDocumentDescription; exports.validateDocumentBody = validateDocumentBody; exports.validateDocumentDescription = validateDocumentDescription; exports.validateDocumentName = validateDocumentName; exports.writeBodyText = writeBodyText;
657
+ //# sourceMappingURL=docs.cjs.map