@sofereditor/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1772 @@
1
+ // src/types.ts
2
+ var MAX_LIST_LEVEL = 5;
3
+ function isImageEmbed(v) {
4
+ return typeof v === "object" && v !== null && v.type === "image" && typeof v.src === "string";
5
+ }
6
+ function isLegacySerializedDocument(v) {
7
+ return Array.isArray(v);
8
+ }
9
+
10
+ // src/schema.ts
11
+ var BLOCK_SPECS = {
12
+ paragraph: {
13
+ type: "paragraph",
14
+ defaultAttrs: {}
15
+ },
16
+ heading: {
17
+ type: "heading",
18
+ defaultAttrs: { level: 1 }
19
+ },
20
+ blockquote: {
21
+ type: "blockquote",
22
+ defaultAttrs: {}
23
+ },
24
+ codeBlock: {
25
+ type: "codeBlock",
26
+ defaultAttrs: {}
27
+ },
28
+ listItem: {
29
+ type: "listItem",
30
+ defaultAttrs: { listKind: "bullet", listLevel: 0 }
31
+ },
32
+ table: {
33
+ type: "table",
34
+ defaultAttrs: { rows: 1, cols: 1 }
35
+ }
36
+ };
37
+ function isKnownBlockType(t) {
38
+ return t in BLOCK_SPECS;
39
+ }
40
+ function defaultAttrsFor(type) {
41
+ return { ...BLOCK_SPECS[type].defaultAttrs };
42
+ }
43
+
44
+ // src/page-settings.ts
45
+ var PAGE_PRESETS = {
46
+ a4: { width: 794, height: 1123 },
47
+ // 210×297 mm
48
+ letter: { width: 816, height: 1056 },
49
+ // 8.5×11 in
50
+ oficio: { width: 816, height: 1248 }
51
+ // 216×330 mm (BR Ofício)
52
+ };
53
+ var MARGIN_PRESETS = {
54
+ normal: 96,
55
+ // 25.4 mm = 1 in
56
+ narrow: 48,
57
+ // 12.7 mm = 0.5 in
58
+ wide: 192
59
+ // 50.8 mm = 2 in
60
+ };
61
+ var DEFAULT_PAGE_SETTINGS = {
62
+ ...PAGE_PRESETS.a4,
63
+ marginTop: MARGIN_PRESETS.normal,
64
+ marginBottom: MARGIN_PRESETS.normal,
65
+ marginLeft: MARGIN_PRESETS.normal,
66
+ marginRight: MARGIN_PRESETS.normal,
67
+ preset: "a4"
68
+ };
69
+ var PX_PER_MM = 96 / 25.4;
70
+ function mmToPx(mm) {
71
+ return mm * PX_PER_MM;
72
+ }
73
+ function pxToMm(px) {
74
+ return px / PX_PER_MM;
75
+ }
76
+ function detectPreset(width, height) {
77
+ const TOL = mmToPx(0.5);
78
+ for (const [name, size] of Object.entries(PAGE_PRESETS)) {
79
+ if (Math.abs(width - size.width) <= TOL && Math.abs(height - size.height) <= TOL) {
80
+ return name;
81
+ }
82
+ }
83
+ return "custom";
84
+ }
85
+ function withDefaults(partial) {
86
+ const merged = { ...DEFAULT_PAGE_SETTINGS, ...partial ?? {} };
87
+ return { ...merged, preset: detectPreset(merged.width, merged.height) };
88
+ }
89
+
90
+ // src/document.ts
91
+ import * as Y from "yjs";
92
+ var EditorDocument = class _EditorDocument {
93
+ ydoc;
94
+ blocks;
95
+ /** Document-level settings (page size, margins). See `getPageSettings`. */
96
+ docSettings;
97
+ constructor(ydoc) {
98
+ this.ydoc = ydoc ?? new Y.Doc();
99
+ this.blocks = this.ydoc.getArray("blocks");
100
+ this.docSettings = this.ydoc.getMap("docSettings");
101
+ if (this.blocks.length === 0) {
102
+ this.ydoc.transact(() => {
103
+ this.blocks.push([createBlock("paragraph")]);
104
+ }, "init");
105
+ }
106
+ }
107
+ /**
108
+ * Returns the current page settings. Missing fields fall back to
109
+ * `DEFAULT_PAGE_SETTINGS`. The returned object is a fresh snapshot — mutating
110
+ * it does not write back to the Y.Doc. Use `setPageSettings` for that.
111
+ */
112
+ getPageSettings() {
113
+ const m = this.docSettings;
114
+ const pick = (key) => {
115
+ const v = m.get(key);
116
+ return typeof v === "number" || typeof v === "string" ? v : void 0;
117
+ };
118
+ const merged = {
119
+ width: pick("width") ?? DEFAULT_PAGE_SETTINGS.width,
120
+ height: pick("height") ?? DEFAULT_PAGE_SETTINGS.height,
121
+ marginTop: pick("marginTop") ?? DEFAULT_PAGE_SETTINGS.marginTop,
122
+ marginBottom: pick("marginBottom") ?? DEFAULT_PAGE_SETTINGS.marginBottom,
123
+ marginLeft: pick("marginLeft") ?? DEFAULT_PAGE_SETTINGS.marginLeft,
124
+ marginRight: pick("marginRight") ?? DEFAULT_PAGE_SETTINGS.marginRight
125
+ };
126
+ merged.preset = detectPreset(merged.width, merged.height);
127
+ return merged;
128
+ }
129
+ /**
130
+ * Merge `partial` into the docSettings Y.Map. Runs in a `ydoc.transact` with
131
+ * origin `"pageSettings"` (NOT tracked by the UndoManager, mirroring the
132
+ * `"import"` convention from Fase 9.5).
133
+ */
134
+ setPageSettings(partial) {
135
+ this.ydoc.transact(() => {
136
+ for (const [k, v] of Object.entries(partial)) {
137
+ if (k === "preset") continue;
138
+ if (v === void 0) continue;
139
+ this.docSettings.set(k, v);
140
+ }
141
+ }, "pageSettings");
142
+ }
143
+ /** Listen for page-settings changes. Returns an unsubscribe function. */
144
+ observePageSettings(cb) {
145
+ const handler = () => cb();
146
+ this.docSettings.observe(handler);
147
+ return () => this.docSettings.unobserve(handler);
148
+ }
149
+ getBlock(index) {
150
+ if (index < 0 || index >= this.blocks.length) return void 0;
151
+ return this.blocks.get(index);
152
+ }
153
+ getBlockText(index) {
154
+ const b = this.getBlock(index);
155
+ return b?.get("text");
156
+ }
157
+ getBlockType(index) {
158
+ const b = this.getBlock(index);
159
+ return b?.get("type");
160
+ }
161
+ getBlockAttrsMap(index) {
162
+ const b = this.getBlock(index);
163
+ return b?.get("attrs");
164
+ }
165
+ getBlockAttrs(index) {
166
+ const m = this.getBlockAttrsMap(index);
167
+ if (!m) return {};
168
+ return Object.fromEntries(m.entries());
169
+ }
170
+ blockCount() {
171
+ return this.blocks.length;
172
+ }
173
+ // ---------- Table helpers ----------
174
+ isTable(index) {
175
+ return this.getBlockType(index) === "table";
176
+ }
177
+ /** Returns rows/cols read from the block's attrs map. `{0,0}` for non-table blocks. */
178
+ getTableSize(index) {
179
+ const attrs = this.getBlockAttrs(index);
180
+ const rows = typeof attrs.rows === "number" ? attrs.rows : 0;
181
+ const cols = typeof attrs.cols === "number" ? attrs.cols : 0;
182
+ return { rows, cols };
183
+ }
184
+ getCells(index) {
185
+ const b = this.getBlock(index);
186
+ return b?.get("cells");
187
+ }
188
+ getCell(blockIndex, cellIndex) {
189
+ const cells = this.getCells(blockIndex);
190
+ if (!cells) return void 0;
191
+ if (cellIndex < 0 || cellIndex >= cells.length) return void 0;
192
+ return cells.get(cellIndex);
193
+ }
194
+ getCellText(blockIndex, cellIndex) {
195
+ const cell = this.getCell(blockIndex, cellIndex);
196
+ return cell?.get("text");
197
+ }
198
+ getCellAttrsMap(blockIndex, cellIndex) {
199
+ const cell = this.getCell(blockIndex, cellIndex);
200
+ return cell?.get("attrs");
201
+ }
202
+ getCellAttrs(blockIndex, cellIndex) {
203
+ const m = this.getCellAttrsMap(blockIndex, cellIndex);
204
+ if (!m) return {};
205
+ return Object.fromEntries(m.entries());
206
+ }
207
+ /**
208
+ * Returns the editable Y.Text targeted by a position:
209
+ * - When `cellIndex` is set: the cell's text (or the owner's text if the
210
+ * target cell is covered by a span — we treat the covered position as
211
+ * addressing the owner).
212
+ * - Otherwise: the block's own text.
213
+ */
214
+ textAt(blockIndex, cellIndex) {
215
+ if (cellIndex == null) return this.getBlockText(blockIndex);
216
+ const real = this.realCellIndex(blockIndex, cellIndex);
217
+ if (real == null) return void 0;
218
+ return this.getCellText(blockIndex, real);
219
+ }
220
+ /**
221
+ * Returns the "real" (non-covered) cell index for a target cell. When the
222
+ * target is already a real cell, returns it unchanged. When covered, walks
223
+ * up-and-left to find the owner.
224
+ *
225
+ * The walk is robust to oversized or non-rectangular spans by skipping
226
+ * candidates whose declared span doesn't actually cover the target.
227
+ */
228
+ realCellIndex(blockIndex, cellIndex) {
229
+ if (!this.isTable(blockIndex)) return void 0;
230
+ const { rows, cols } = this.getTableSize(blockIndex);
231
+ if (rows <= 0 || cols <= 0) return void 0;
232
+ if (cellIndex < 0 || cellIndex >= rows * cols) return void 0;
233
+ const targetAttrs = this.getCellAttrs(blockIndex, cellIndex);
234
+ if (targetAttrs.covered !== true) return cellIndex;
235
+ const tRow = Math.floor(cellIndex / cols);
236
+ const tCol = cellIndex - tRow * cols;
237
+ for (let or = tRow; or >= 0; or--) {
238
+ for (let oc = tCol; oc >= 0; oc--) {
239
+ const idx = or * cols + oc;
240
+ if (idx === cellIndex) continue;
241
+ const a = this.getCellAttrs(blockIndex, idx);
242
+ if (a.covered === true) continue;
243
+ const rs = spanOf(a.rowspan);
244
+ const cs = spanOf(a.colspan);
245
+ if (or + rs - 1 >= tRow && oc + cs - 1 >= tCol) return idx;
246
+ }
247
+ }
248
+ return void 0;
249
+ }
250
+ /**
251
+ * Replace the document contents with a `SerializedDocument`. Runs in a single
252
+ * `ydoc.transact` with origin `"import"` so the UndoManager can group or skip
253
+ * the operation as one step.
254
+ *
255
+ * Reuses `createBlock` / `createTableBlock` so every block ends up with the
256
+ * exact same Y.js shape produced by the rest of the editor.
257
+ */
258
+ loadFromJSON(serialized) {
259
+ const { blocks: sourceBlocks, pageSettings } = normalizeSerialized(serialized);
260
+ this.ydoc.transact(() => {
261
+ if (this.blocks.length > 0) {
262
+ this.blocks.delete(0, this.blocks.length);
263
+ }
264
+ const sources = sourceBlocks.length > 0 ? sourceBlocks : [makeEmptyParagraphSource()];
265
+ const fresh = sources.map((b) => buildEmptyBlockFromSerialized(b));
266
+ this.blocks.push(fresh);
267
+ for (let i = 0; i < sources.length; i++) {
268
+ const src = sources[i];
269
+ const yBlock = this.blocks.get(i);
270
+ applyDeltaIntoBlock(yBlock, src);
271
+ }
272
+ if (pageSettings) {
273
+ for (const [k, v] of Object.entries(pageSettings)) {
274
+ if (k === "preset") continue;
275
+ if (v === void 0) continue;
276
+ this.docSettings.set(k, v);
277
+ }
278
+ }
279
+ }, "import");
280
+ }
281
+ /** Build a fresh `EditorDocument` from a `SerializedDocument`. */
282
+ static fromJSON(serialized) {
283
+ const ydoc = new Y.Doc();
284
+ ydoc.getArray("blocks");
285
+ const doc = new _EditorDocument(ydoc);
286
+ doc.loadFromJSON(serialized);
287
+ return doc;
288
+ }
289
+ toJSON() {
290
+ const blocks = this.blocks.toArray().map((b) => {
291
+ const type = b.get("type") ?? "paragraph";
292
+ const attrsMap = b.get("attrs");
293
+ const attrs = attrsMap ? Object.fromEntries(attrsMap.entries()) : {};
294
+ if (type === "table") {
295
+ const cellsArray = b.get("cells");
296
+ const cells = cellsArray ? cellsArray.toArray().map((c) => serializeCell(c)) : [];
297
+ return {
298
+ type,
299
+ text: "",
300
+ delta: [],
301
+ attrs,
302
+ cells
303
+ };
304
+ }
305
+ const yText = b.get("text");
306
+ return {
307
+ type,
308
+ text: yText?.toString() ?? "",
309
+ delta: yText?.toDelta() ?? [],
310
+ attrs
311
+ };
312
+ });
313
+ return { blocks, pageSettings: this.getPageSettings() };
314
+ }
315
+ };
316
+ function normalizeSerialized(v) {
317
+ if (isLegacySerializedDocument(v)) {
318
+ return { blocks: v };
319
+ }
320
+ return { blocks: v.blocks, pageSettings: v.pageSettings };
321
+ }
322
+ function serializeCell(c) {
323
+ const yText = c.get("text");
324
+ const attrsMap = c.get("attrs");
325
+ return {
326
+ text: yText?.toString() ?? "",
327
+ delta: yText?.toDelta() ?? [],
328
+ attrs: attrsMap ? Object.fromEntries(attrsMap.entries()) : {}
329
+ };
330
+ }
331
+ function createBlock(type, initialText = "", attrs) {
332
+ if (type === "table") {
333
+ const merged2 = { ...defaultAttrsFor("table"), ...attrs ?? {} };
334
+ const rows = typeof merged2.rows === "number" && merged2.rows > 0 ? Math.trunc(merged2.rows) : 1;
335
+ const cols = typeof merged2.cols === "number" && merged2.cols > 0 ? Math.trunc(merged2.cols) : 1;
336
+ return createTableBlock(rows, cols);
337
+ }
338
+ const block = new Y.Map();
339
+ block.set("type", type);
340
+ const yText = new Y.Text();
341
+ if (initialText.length > 0) yText.insert(0, initialText);
342
+ block.set("text", yText);
343
+ const attrsMap = new Y.Map();
344
+ const merged = { ...defaultAttrsFor(type), ...attrs ?? {} };
345
+ for (const [k, v] of Object.entries(merged)) {
346
+ if (v !== void 0) attrsMap.set(k, v);
347
+ }
348
+ block.set("attrs", attrsMap);
349
+ return block;
350
+ }
351
+ function createTableBlock(rows, cols) {
352
+ const r = Math.max(1, Math.trunc(rows));
353
+ const c = Math.max(1, Math.trunc(cols));
354
+ const block = new Y.Map();
355
+ block.set("type", "table");
356
+ const attrsMap = new Y.Map();
357
+ attrsMap.set("rows", r);
358
+ attrsMap.set("cols", c);
359
+ block.set("attrs", attrsMap);
360
+ const cells = new Y.Array();
361
+ const total = r * c;
362
+ const fresh = [];
363
+ for (let i = 0; i < total; i++) fresh.push(createCell());
364
+ cells.push(fresh);
365
+ block.set("cells", cells);
366
+ return block;
367
+ }
368
+ function createCell(attrs) {
369
+ const m = new Y.Map();
370
+ m.set("text", new Y.Text());
371
+ const attrsMap = new Y.Map();
372
+ if (attrs) {
373
+ for (const [k, v] of Object.entries(attrs)) {
374
+ if (v === void 0) continue;
375
+ attrsMap.set(k, v);
376
+ }
377
+ }
378
+ m.set("attrs", attrsMap);
379
+ return m;
380
+ }
381
+ function makeEmptyParagraphSource() {
382
+ return { type: "paragraph", text: "", delta: [], attrs: {} };
383
+ }
384
+ function buildEmptyBlockFromSerialized(block) {
385
+ if (block.type === "table") {
386
+ const rows = typeof block.attrs.rows === "number" ? Math.max(1, Math.trunc(block.attrs.rows)) : 1;
387
+ const cols = typeof block.attrs.cols === "number" ? Math.max(1, Math.trunc(block.attrs.cols)) : 1;
388
+ const yBlock = new Y.Map();
389
+ yBlock.set("type", "table");
390
+ const attrsMap = new Y.Map();
391
+ attrsMap.set("rows", rows);
392
+ attrsMap.set("cols", cols);
393
+ for (const [k, v] of Object.entries(block.attrs)) {
394
+ if (k === "rows" || k === "cols") continue;
395
+ if (v === void 0) continue;
396
+ attrsMap.set(k, v);
397
+ }
398
+ yBlock.set("attrs", attrsMap);
399
+ const cellsArr = new Y.Array();
400
+ const total = rows * cols;
401
+ const fresh = [];
402
+ const cells = block.cells ?? [];
403
+ for (let i = 0; i < total; i++) {
404
+ const src = cells[i];
405
+ fresh.push(createCell(src?.attrs ?? void 0));
406
+ }
407
+ cellsArr.push(fresh);
408
+ yBlock.set("cells", cellsArr);
409
+ return yBlock;
410
+ }
411
+ return createBlock(block.type, "", block.attrs);
412
+ }
413
+ function applyDeltaIntoBlock(yBlock, src) {
414
+ if (!yBlock) return;
415
+ if (src.type === "table") {
416
+ const cellsArr = yBlock.get("cells");
417
+ if (!cellsArr) return;
418
+ const cells = src.cells ?? [];
419
+ for (let i = 0; i < cellsArr.length; i++) {
420
+ const cellSrc = cells[i];
421
+ if (!cellSrc || !cellSrc.delta || cellSrc.delta.length === 0) continue;
422
+ const cell = cellsArr.get(i);
423
+ const yText2 = cell.get("text");
424
+ if (!yText2) continue;
425
+ yText2.applyDelta(cellSrc.delta);
426
+ }
427
+ return;
428
+ }
429
+ if (!src.delta || src.delta.length === 0) return;
430
+ const yText = yBlock.get("text");
431
+ if (!yText) return;
432
+ yText.applyDelta(src.delta);
433
+ }
434
+ function spanOf(value) {
435
+ if (typeof value !== "number" || !Number.isFinite(value)) return 1;
436
+ return Math.max(1, Math.trunc(value));
437
+ }
438
+
439
+ // src/selection.ts
440
+ function isCollapsed(sel) {
441
+ return positionsEqual(sel.anchor, sel.focus);
442
+ }
443
+ function positionsEqual(a, b) {
444
+ return a.blockIndex === b.blockIndex && (a.cellIndex ?? -1) === (b.cellIndex ?? -1) && a.offset === b.offset;
445
+ }
446
+ function comparePositions(a, b) {
447
+ if (a.blockIndex !== b.blockIndex) return a.blockIndex - b.blockIndex;
448
+ const ac = a.cellIndex ?? -1;
449
+ const bc = b.cellIndex ?? -1;
450
+ if (ac !== bc) return ac - bc;
451
+ return a.offset - b.offset;
452
+ }
453
+ function orderedRange(sel) {
454
+ if (comparePositions(sel.anchor, sel.focus) <= 0) {
455
+ return { start: sel.anchor, end: sel.focus };
456
+ }
457
+ return { start: sel.focus, end: sel.anchor };
458
+ }
459
+ function collapsedSelection(pos) {
460
+ return { anchor: pos, focus: pos };
461
+ }
462
+ function sameTextRun(a, b) {
463
+ return a.blockIndex === b.blockIndex && (a.cellIndex ?? -1) === (b.cellIndex ?? -1);
464
+ }
465
+
466
+ // src/marks.ts
467
+ function iterateRunsInRange(yText, start, end, visit) {
468
+ if (start >= end) return;
469
+ const delta = yText.toDelta();
470
+ let cursor = 0;
471
+ for (const op of delta) {
472
+ if (typeof op.insert !== "string") {
473
+ if (op.insert == null) continue;
474
+ cursor += 1;
475
+ if (cursor > end) break;
476
+ continue;
477
+ }
478
+ const opStart = cursor;
479
+ const opEnd = cursor + op.insert.length;
480
+ cursor = opEnd;
481
+ if (opEnd <= start) continue;
482
+ if (opStart >= end) break;
483
+ const runStart = Math.max(opStart, start);
484
+ const runEnd = Math.min(opEnd, end);
485
+ visit(op, runStart, runEnd);
486
+ }
487
+ }
488
+ function getMarksInRange(yText, start, end) {
489
+ const result = {};
490
+ if (start >= end) return result;
491
+ const total = end - start;
492
+ const coveredByName = {};
493
+ iterateRunsInRange(yText, start, end, (run, runStart, runEnd) => {
494
+ const len = runEnd - runStart;
495
+ const attrs = run.attributes ?? {};
496
+ Object.keys(attrs).forEach((name) => {
497
+ const value = attrs[name];
498
+ if (value === void 0 || value === null) return;
499
+ const prev = result[name];
500
+ if (prev === void 0) {
501
+ result[name] = value;
502
+ } else if (prev !== "mixed" && !sameMarkValue(prev, value)) {
503
+ result[name] = "mixed";
504
+ }
505
+ coveredByName[name] = (coveredByName[name] ?? 0) + len;
506
+ });
507
+ });
508
+ Object.keys(result).forEach((name) => {
509
+ if ((coveredByName[name] ?? 0) < total) {
510
+ result[name] = "mixed";
511
+ }
512
+ });
513
+ return result;
514
+ }
515
+ function isMarkUniformInRange(yText, start, end, name, value) {
516
+ if (start >= end) return false;
517
+ const total = end - start;
518
+ let covered = 0;
519
+ let observed;
520
+ let ok = true;
521
+ iterateRunsInRange(yText, start, end, (run, runStart, runEnd) => {
522
+ if (!ok) return;
523
+ const len = runEnd - runStart;
524
+ const attr = (run.attributes ?? {})[name];
525
+ if (attr === void 0 || attr === null) {
526
+ ok = false;
527
+ return;
528
+ }
529
+ if (value !== void 0 && !sameMarkValue(value, attr)) {
530
+ ok = false;
531
+ return;
532
+ }
533
+ if (observed === void 0) {
534
+ observed = attr;
535
+ } else if (!sameMarkValue(observed, attr)) {
536
+ ok = false;
537
+ return;
538
+ }
539
+ covered += len;
540
+ });
541
+ return ok && covered === total;
542
+ }
543
+ function deltaLength(delta) {
544
+ let sum = 0;
545
+ for (const op of delta) {
546
+ if (typeof op.insert === "string") sum += op.insert.length;
547
+ else if (op.insert != null) sum += 1;
548
+ }
549
+ return sum;
550
+ }
551
+ function sliceDelta(delta, start, end) {
552
+ if (end <= start) return [];
553
+ const out = [];
554
+ let cursor = 0;
555
+ for (const op of delta) {
556
+ if (typeof op.insert === "string") {
557
+ const opStart = cursor;
558
+ const opEnd = cursor + op.insert.length;
559
+ cursor = opEnd;
560
+ if (opEnd <= start) continue;
561
+ if (opStart >= end) break;
562
+ const sliceFrom = Math.max(opStart, start) - opStart;
563
+ const sliceTo = Math.min(opEnd, end) - opStart;
564
+ out.push({
565
+ insert: op.insert.slice(sliceFrom, sliceTo),
566
+ attributes: op.attributes
567
+ });
568
+ } else if (op.insert != null) {
569
+ const opStart = cursor;
570
+ const opEnd = cursor + 1;
571
+ cursor = opEnd;
572
+ if (opEnd <= start) continue;
573
+ if (opStart >= end) break;
574
+ out.push({ insert: op.insert, attributes: op.attributes });
575
+ }
576
+ }
577
+ return out;
578
+ }
579
+ function sameMarkValue(a, b) {
580
+ if (a === b) return true;
581
+ if (typeof a === "object" && typeof b === "object" && a && b) {
582
+ const aKeys = Object.keys(a);
583
+ const bKeys = Object.keys(b);
584
+ if (aKeys.length !== bKeys.length) return false;
585
+ return aKeys.every((k) => a[k] === b[k]);
586
+ }
587
+ return false;
588
+ }
589
+
590
+ // src/commands.ts
591
+ import * as Y2 from "yjs";
592
+ var COMMAND_ORIGIN = /* @__PURE__ */ Symbol("editor-command");
593
+ function transact(doc, fn) {
594
+ doc.ydoc.transact(fn, COMMAND_ORIGIN);
595
+ }
596
+ function insertText(ctx, text, marks) {
597
+ if (text.length === 0) return;
598
+ const collapsedFromRect = clearRectCells(ctx);
599
+ transact(ctx.doc, () => {
600
+ const sel = collapsedFromRect ?? ctx.getSelection();
601
+ let cursor = sel;
602
+ if (!isCollapsed(sel)) {
603
+ cursor = deleteRange(ctx.doc, sel);
604
+ }
605
+ const pos = cursor.focus;
606
+ const yText = ctx.doc.textAt(pos.blockIndex, pos.cellIndex);
607
+ if (!yText) return;
608
+ yText.insert(pos.offset, text);
609
+ if (marks && Object.keys(marks).length > 0) {
610
+ yText.format(pos.offset, text.length, marks);
611
+ }
612
+ const newPos = {
613
+ blockIndex: pos.blockIndex,
614
+ cellIndex: pos.cellIndex,
615
+ offset: pos.offset + text.length
616
+ };
617
+ ctx.setSelection(collapsedSelection(newPos));
618
+ });
619
+ }
620
+ function insertParagraph(ctx) {
621
+ clearRectCells(ctx);
622
+ transact(ctx.doc, () => {
623
+ const sel = ctx.getSelection();
624
+ let cursor = sel;
625
+ if (!isCollapsed(sel)) {
626
+ cursor = deleteRange(ctx.doc, sel);
627
+ }
628
+ const pos = cursor.focus;
629
+ if (pos.cellIndex != null) {
630
+ const yText2 = ctx.doc.getCellText(pos.blockIndex, pos.cellIndex);
631
+ if (!yText2) return;
632
+ yText2.insert(pos.offset, "\n");
633
+ ctx.setSelection(
634
+ collapsedSelection({
635
+ blockIndex: pos.blockIndex,
636
+ cellIndex: pos.cellIndex,
637
+ offset: pos.offset + 1
638
+ })
639
+ );
640
+ return;
641
+ }
642
+ const yText = ctx.doc.getBlockText(pos.blockIndex);
643
+ if (!yText) return;
644
+ const totalLen = yText.length;
645
+ const tailDelta = totalLen > pos.offset ? sliceDelta(yText.toDelta(), pos.offset, totalLen) : [];
646
+ if (totalLen > pos.offset) {
647
+ yText.delete(pos.offset, totalLen - pos.offset);
648
+ }
649
+ const newBlock = createBlock("paragraph");
650
+ ctx.doc.blocks.insert(pos.blockIndex + 1, [newBlock]);
651
+ if (tailDelta.length > 0) {
652
+ const newYText = newBlock.get("text");
653
+ writeDeltaInto(newYText, 0, tailDelta);
654
+ }
655
+ const newPos = { blockIndex: pos.blockIndex + 1, offset: 0 };
656
+ ctx.setSelection(collapsedSelection(newPos));
657
+ });
658
+ }
659
+ function deleteBackward(ctx) {
660
+ if (clearRectCells(ctx)) return;
661
+ transact(ctx.doc, () => {
662
+ const sel = ctx.getSelection();
663
+ if (!isCollapsed(sel)) {
664
+ const cursor = deleteRange(ctx.doc, sel);
665
+ ctx.setSelection(cursor);
666
+ return;
667
+ }
668
+ const pos = sel.focus;
669
+ if (pos.offset > 0) {
670
+ const yText = ctx.doc.textAt(pos.blockIndex, pos.cellIndex);
671
+ if (!yText) return;
672
+ yText.delete(pos.offset - 1, 1);
673
+ ctx.setSelection(
674
+ collapsedSelection({
675
+ blockIndex: pos.blockIndex,
676
+ cellIndex: pos.cellIndex,
677
+ offset: pos.offset - 1
678
+ })
679
+ );
680
+ return;
681
+ }
682
+ if (pos.cellIndex != null) return;
683
+ const blockType = ctx.doc.getBlockType(pos.blockIndex);
684
+ if (blockType === "listItem") {
685
+ const block = ctx.doc.getBlock(pos.blockIndex);
686
+ const attrsMap = block?.get("attrs");
687
+ const rawLevel = attrsMap?.get("listLevel");
688
+ const level = typeof rawLevel === "number" && Number.isFinite(rawLevel) ? Math.max(0, Math.min(MAX_LIST_LEVEL, Math.trunc(rawLevel))) : 0;
689
+ if (level > 0) {
690
+ attrsMap.set("listLevel", level - 1);
691
+ ctx.setSelection(collapsedSelection(pos));
692
+ return;
693
+ }
694
+ setBlockTypeAtIndex(ctx.doc, pos.blockIndex, "paragraph", {});
695
+ ctx.setSelection(collapsedSelection(pos));
696
+ return;
697
+ }
698
+ if (pos.blockIndex > 0) {
699
+ const prevType = ctx.doc.getBlockType(pos.blockIndex - 1);
700
+ if (prevType === "table") {
701
+ const tableIdx = pos.blockIndex - 1;
702
+ const { rows, cols } = ctx.doc.getTableSize(tableIdx);
703
+ const lastCell = rows * cols - 1;
704
+ const cellText = ctx.doc.getCellText(tableIdx, lastCell);
705
+ const off = cellText?.length ?? 0;
706
+ ctx.setSelection(
707
+ collapsedSelection({ blockIndex: tableIdx, cellIndex: lastCell, offset: off })
708
+ );
709
+ return;
710
+ }
711
+ mergeWithPrevious(ctx, pos.blockIndex);
712
+ }
713
+ });
714
+ }
715
+ function deleteForward(ctx) {
716
+ if (clearRectCells(ctx)) return;
717
+ transact(ctx.doc, () => {
718
+ const sel = ctx.getSelection();
719
+ if (!isCollapsed(sel)) {
720
+ const cursor = deleteRange(ctx.doc, sel);
721
+ ctx.setSelection(cursor);
722
+ return;
723
+ }
724
+ const pos = sel.focus;
725
+ const yText = ctx.doc.textAt(pos.blockIndex, pos.cellIndex);
726
+ if (!yText) return;
727
+ if (pos.offset < yText.length) {
728
+ yText.delete(pos.offset, 1);
729
+ ctx.setSelection(collapsedSelection(pos));
730
+ return;
731
+ }
732
+ if (pos.cellIndex != null) return;
733
+ if (pos.blockIndex + 1 < ctx.doc.blockCount()) {
734
+ const nextType = ctx.doc.getBlockType(pos.blockIndex + 1);
735
+ if (nextType === "table") {
736
+ const tableIdx = pos.blockIndex + 1;
737
+ ctx.setSelection(collapsedSelection({ blockIndex: tableIdx, cellIndex: 0, offset: 0 }));
738
+ return;
739
+ }
740
+ const nextText = ctx.doc.getBlockText(pos.blockIndex + 1);
741
+ if (!nextText) return;
742
+ const tail = nextText.toString();
743
+ if (tail.length > 0) yText.insert(yText.length, tail);
744
+ ctx.doc.blocks.delete(pos.blockIndex + 1, 1);
745
+ ctx.setSelection(collapsedSelection(pos));
746
+ }
747
+ });
748
+ }
749
+ var booleanMarks = /* @__PURE__ */ new Set(["bold", "italic", "underline", "strike"]);
750
+ function toggleMark(ctx, name, value) {
751
+ const sel = ctx.getSelection();
752
+ if (isCollapsed(sel)) return;
753
+ transact(ctx.doc, () => {
754
+ const { start, end } = orderedRange(sel);
755
+ forEachBlockTextInRange(ctx.doc, start, end, (yText, localStart, localEnd) => {
756
+ const uniform = isMarkUniformInRange(yText, localStart, localEnd, name, value);
757
+ const attrs = {};
758
+ if (uniform) {
759
+ attrs[name] = null;
760
+ } else {
761
+ attrs[name] = value ?? (booleanMarks.has(name) ? true : null);
762
+ }
763
+ if (attrs[name] === null && !uniform) return;
764
+ yText.format(localStart, localEnd - localStart, attrs);
765
+ });
766
+ ctx.setSelection(sel);
767
+ });
768
+ }
769
+ function setMark(ctx, name, value) {
770
+ const sel = ctx.getSelection();
771
+ if (isCollapsed(sel)) return;
772
+ transact(ctx.doc, () => {
773
+ const { start, end } = orderedRange(sel);
774
+ forEachBlockTextInRange(ctx.doc, start, end, (yText, localStart, localEnd) => {
775
+ yText.format(localStart, localEnd - localStart, { [name]: value });
776
+ });
777
+ ctx.setSelection(sel);
778
+ });
779
+ }
780
+ function removeMark(ctx, name) {
781
+ const sel = ctx.getSelection();
782
+ if (isCollapsed(sel)) return;
783
+ transact(ctx.doc, () => {
784
+ const { start, end } = orderedRange(sel);
785
+ forEachBlockTextInRange(ctx.doc, start, end, (yText, localStart, localEnd) => {
786
+ yText.format(localStart, localEnd - localStart, { [name]: null });
787
+ });
788
+ ctx.setSelection(sel);
789
+ });
790
+ }
791
+ function setBlockType(ctx, type, attrs) {
792
+ transact(ctx.doc, () => {
793
+ const sel = ctx.getSelection();
794
+ if (sel.anchor.cellIndex != null || sel.focus.cellIndex != null) return;
795
+ const indices = selectedBlockIndices(sel, ctx.doc.blockCount());
796
+ for (const i of indices) {
797
+ const block = ctx.doc.getBlock(i);
798
+ if (!block) continue;
799
+ block.set("type", type);
800
+ const attrsMap = block.get("attrs") ?? new Y2.Map();
801
+ if (!block.get("attrs")) block.set("attrs", attrsMap);
802
+ const next = { ...defaultAttrsFor(type), ...attrs ?? {} };
803
+ const keep = new Set(Object.keys(next));
804
+ for (const k of Array.from(attrsMap.keys())) {
805
+ if (!keep.has(k)) attrsMap.delete(k);
806
+ }
807
+ for (const [k, v] of Object.entries(next)) {
808
+ if (v === void 0) continue;
809
+ if (attrsMap.get(k) !== v) attrsMap.set(k, v);
810
+ }
811
+ }
812
+ ctx.setSelection(sel);
813
+ });
814
+ }
815
+ function setBlockAttr(ctx, key, value) {
816
+ transact(ctx.doc, () => {
817
+ const sel = ctx.getSelection();
818
+ if (sel.anchor.cellIndex != null || sel.focus.cellIndex != null) return;
819
+ const indices = selectedBlockIndices(sel, ctx.doc.blockCount());
820
+ for (const i of indices) {
821
+ const block = ctx.doc.getBlock(i);
822
+ if (!block) continue;
823
+ const attrsMap = block.get("attrs") ?? new Y2.Map();
824
+ if (!block.get("attrs")) block.set("attrs", attrsMap);
825
+ if (value === null || value === void 0) {
826
+ attrsMap.delete(key);
827
+ } else {
828
+ attrsMap.set(key, value);
829
+ }
830
+ }
831
+ ctx.setSelection(sel);
832
+ });
833
+ }
834
+ function selectedBlockIndices(sel, blockCount) {
835
+ const { start, end } = orderedRange(sel);
836
+ const out = [];
837
+ const last = Math.min(end.blockIndex, blockCount - 1);
838
+ for (let i = Math.max(start.blockIndex, 0); i <= last; i++) out.push(i);
839
+ return out;
840
+ }
841
+ function forEachBlockTextInRange(doc, start, end, visit) {
842
+ if (sameTextRun(start, end)) {
843
+ const yText = doc.textAt(start.blockIndex, start.cellIndex);
844
+ if (!yText) return;
845
+ if (start.offset < end.offset) visit(yText, start.offset, end.offset);
846
+ return;
847
+ }
848
+ if (start.cellIndex != null || end.cellIndex != null) return;
849
+ for (let i = start.blockIndex; i <= end.blockIndex; i++) {
850
+ const yText = doc.getBlockText(i);
851
+ if (!yText) continue;
852
+ const localStart = i === start.blockIndex ? start.offset : 0;
853
+ const localEnd = i === end.blockIndex ? end.offset : yText.length;
854
+ if (localStart < localEnd) visit(yText, localStart, localEnd);
855
+ }
856
+ }
857
+ function deleteRange(doc, sel) {
858
+ const { start, end } = orderedRange(sel);
859
+ if (sameTextRun(start, end)) {
860
+ const yText = doc.textAt(start.blockIndex, start.cellIndex);
861
+ if (yText) yText.delete(start.offset, end.offset - start.offset);
862
+ return collapsedSelection(start);
863
+ }
864
+ if (start.cellIndex != null || end.cellIndex != null) {
865
+ return collapsedSelection(start);
866
+ }
867
+ const startText = doc.getBlockText(start.blockIndex);
868
+ const endText = doc.getBlockText(end.blockIndex);
869
+ if (!startText || !endText) return collapsedSelection(start);
870
+ startText.delete(start.offset, startText.length - start.offset);
871
+ const remainingTail = endText.toString().slice(end.offset);
872
+ if (remainingTail.length > 0) {
873
+ startText.insert(startText.length, remainingTail);
874
+ }
875
+ doc.blocks.delete(start.blockIndex + 1, end.blockIndex - start.blockIndex);
876
+ return collapsedSelection(start);
877
+ }
878
+ function mergeWithPrevious(ctx, blockIndex) {
879
+ const prevText = ctx.doc.getBlockText(blockIndex - 1);
880
+ const currText = ctx.doc.getBlockText(blockIndex);
881
+ if (!prevText || !currText) return;
882
+ const prevLen = prevText.length;
883
+ const tail = currText.toString();
884
+ if (tail.length > 0) prevText.insert(prevLen, tail);
885
+ ctx.doc.blocks.delete(blockIndex, 1);
886
+ ctx.setSelection(collapsedSelection({ blockIndex: blockIndex - 1, offset: prevLen }));
887
+ }
888
+ function toggleList(ctx, kind) {
889
+ transact(ctx.doc, () => {
890
+ const sel = ctx.getSelection();
891
+ if (sel.anchor.cellIndex != null || sel.focus.cellIndex != null) return;
892
+ const indices = selectedBlockIndices(sel, ctx.doc.blockCount());
893
+ const allMatching = indices.every((i) => {
894
+ const block = ctx.doc.getBlock(i);
895
+ if (!block) return false;
896
+ if (block.get("type") !== "listItem") return false;
897
+ const attrs = ctx.doc.getBlockAttrs(i);
898
+ return attrs.listKind === kind;
899
+ });
900
+ for (const i of indices) {
901
+ if (allMatching) {
902
+ setBlockTypeAtIndex(ctx.doc, i, "paragraph", {});
903
+ } else {
904
+ const prev = ctx.doc.getBlockAttrs(i);
905
+ const level = clampLevel(prev.listLevel);
906
+ setBlockTypeAtIndex(ctx.doc, i, "listItem", { listKind: kind, listLevel: level });
907
+ }
908
+ }
909
+ ctx.setSelection(sel);
910
+ });
911
+ }
912
+ function indentList(ctx) {
913
+ transact(ctx.doc, () => {
914
+ const sel = ctx.getSelection();
915
+ if (sel.anchor.cellIndex != null || sel.focus.cellIndex != null) return;
916
+ const indices = selectedBlockIndices(sel, ctx.doc.blockCount());
917
+ for (const i of indices) {
918
+ const block = ctx.doc.getBlock(i);
919
+ if (!block || block.get("type") !== "listItem") continue;
920
+ const attrsMap = block.get("attrs");
921
+ const current = clampLevel(attrsMap.get("listLevel") ?? 0);
922
+ const next = Math.min(current + 1, MAX_LIST_LEVEL);
923
+ if (next !== current) attrsMap.set("listLevel", next);
924
+ }
925
+ ctx.setSelection(sel);
926
+ });
927
+ }
928
+ function dedentList(ctx) {
929
+ transact(ctx.doc, () => {
930
+ const sel = ctx.getSelection();
931
+ if (sel.anchor.cellIndex != null || sel.focus.cellIndex != null) return;
932
+ const indices = selectedBlockIndices(sel, ctx.doc.blockCount());
933
+ for (const i of indices) {
934
+ const block = ctx.doc.getBlock(i);
935
+ if (!block || block.get("type") !== "listItem") continue;
936
+ const attrsMap = block.get("attrs");
937
+ const current = clampLevel(attrsMap.get("listLevel") ?? 0);
938
+ if (current > 0) {
939
+ attrsMap.set("listLevel", current - 1);
940
+ } else {
941
+ setBlockTypeAtIndex(ctx.doc, i, "paragraph", {});
942
+ }
943
+ }
944
+ ctx.setSelection(sel);
945
+ });
946
+ }
947
+ function splitListItem(ctx) {
948
+ const sel = ctx.getSelection();
949
+ if (!isCollapsed(sel)) return false;
950
+ const pos = sel.focus;
951
+ if (pos.cellIndex != null) return false;
952
+ const block = ctx.doc.getBlock(pos.blockIndex);
953
+ if (!block || block.get("type") !== "listItem") return false;
954
+ const yText = ctx.doc.getBlockText(pos.blockIndex);
955
+ if (!yText) return false;
956
+ const attrs = ctx.doc.getBlockAttrs(pos.blockIndex);
957
+ const kind = attrs.listKind ?? "bullet";
958
+ const level = clampLevel(attrs.listLevel ?? 0);
959
+ transact(ctx.doc, () => {
960
+ if (yText.length === 0) {
961
+ if (level > 0) {
962
+ const m = block.get("attrs");
963
+ m.set("listLevel", level - 1);
964
+ } else {
965
+ setBlockTypeAtIndex(ctx.doc, pos.blockIndex, "paragraph", {});
966
+ }
967
+ ctx.setSelection(collapsedSelection({ blockIndex: pos.blockIndex, offset: 0 }));
968
+ return;
969
+ }
970
+ const tail = yText.toString().slice(pos.offset);
971
+ if (tail.length > 0) yText.delete(pos.offset, tail.length);
972
+ ctx.doc.blocks.insert(pos.blockIndex + 1, [
973
+ createBlock("listItem", tail, { listKind: kind, listLevel: level })
974
+ ]);
975
+ ctx.setSelection(collapsedSelection({ blockIndex: pos.blockIndex + 1, offset: 0 }));
976
+ });
977
+ return true;
978
+ }
979
+ function setBlockTypeAtIndex(doc, index, type, attrs) {
980
+ const block = doc.getBlock(index);
981
+ if (!block) return;
982
+ block.set("type", type);
983
+ const attrsMap = block.get("attrs") ?? new Y2.Map();
984
+ if (!block.get("attrs")) block.set("attrs", attrsMap);
985
+ const next = { ...defaultAttrsFor(type), ...attrs };
986
+ const keep = new Set(Object.keys(next));
987
+ for (const k of Array.from(attrsMap.keys())) {
988
+ if (!keep.has(k)) attrsMap.delete(k);
989
+ }
990
+ for (const [k, v] of Object.entries(next)) {
991
+ if (v === void 0) continue;
992
+ if (attrsMap.get(k) !== v) attrsMap.set(k, v);
993
+ }
994
+ }
995
+ function clampLevel(l) {
996
+ if (typeof l !== "number" || !Number.isFinite(l)) return 0;
997
+ return Math.max(0, Math.min(MAX_LIST_LEVEL, Math.trunc(l)));
998
+ }
999
+ function insertTable(ctx, rows, cols) {
1000
+ const r = Math.max(1, Math.trunc(rows));
1001
+ const c = Math.max(1, Math.trunc(cols));
1002
+ transact(ctx.doc, () => {
1003
+ const sel = ctx.getSelection();
1004
+ const focusBlock = Math.max(0, Math.min(sel.focus.blockIndex, ctx.doc.blockCount() - 1));
1005
+ const insertAt = focusBlock + 1;
1006
+ ctx.doc.blocks.insert(insertAt, [createTableBlock(r, c)]);
1007
+ ctx.setSelection(
1008
+ collapsedSelection({ blockIndex: insertAt, cellIndex: 0, offset: 0 })
1009
+ );
1010
+ });
1011
+ }
1012
+ function deleteTable(ctx, blockIndex) {
1013
+ if (!ctx.doc.isTable(blockIndex)) return;
1014
+ transact(ctx.doc, () => {
1015
+ ctx.doc.blocks.delete(blockIndex, 1);
1016
+ if (ctx.doc.blocks.length === 0) {
1017
+ ctx.doc.blocks.push([createBlock("paragraph")]);
1018
+ ctx.setSelection(collapsedSelection({ blockIndex: 0, offset: 0 }));
1019
+ return;
1020
+ }
1021
+ const targetIdx = Math.max(0, Math.min(blockIndex, ctx.doc.blockCount() - 1));
1022
+ const targetType = ctx.doc.getBlockType(targetIdx);
1023
+ if (targetType === "table") {
1024
+ ctx.setSelection(collapsedSelection({ blockIndex: targetIdx, cellIndex: 0, offset: 0 }));
1025
+ } else {
1026
+ ctx.setSelection(collapsedSelection({ blockIndex: targetIdx, offset: 0 }));
1027
+ }
1028
+ });
1029
+ }
1030
+ function tableLocationOf(doc, pos) {
1031
+ if (pos.cellIndex == null) return null;
1032
+ if (!doc.isTable(pos.blockIndex)) return null;
1033
+ const { cols } = doc.getTableSize(pos.blockIndex);
1034
+ if (cols <= 0) return null;
1035
+ const real = doc.realCellIndex(pos.blockIndex, pos.cellIndex);
1036
+ if (real == null) return null;
1037
+ const row = Math.floor(real / cols);
1038
+ const col = real - row * cols;
1039
+ return { blockIndex: pos.blockIndex, row, col };
1040
+ }
1041
+ function insertTableRow(ctx, blockIndex, row, where) {
1042
+ if (!ctx.doc.isTable(blockIndex)) return;
1043
+ const { rows, cols } = ctx.doc.getTableSize(blockIndex);
1044
+ if (rows <= 0 || cols <= 0) return;
1045
+ const targetRow = Math.max(0, Math.min(row, rows - 1));
1046
+ const P = where === "before" ? targetRow : targetRow + 1;
1047
+ transact(ctx.doc, () => {
1048
+ const cells = ctx.doc.getCells(blockIndex);
1049
+ const attrs = ctx.doc.getBlockAttrsMap(blockIndex);
1050
+ if (!cells || !attrs) return;
1051
+ const newRowCovered = new Array(cols).fill(false);
1052
+ if (P > 0 && P < rows) {
1053
+ for (let r = 0; r < P; r++) {
1054
+ for (let c = 0; c < cols; c++) {
1055
+ const a = ctx.doc.getCellAttrs(blockIndex, r * cols + c);
1056
+ if (a.covered === true) continue;
1057
+ const rs = spanOf(a.rowspan);
1058
+ const cs = spanOf(a.colspan);
1059
+ if (r < P && r + rs > P) {
1060
+ for (let cc = c; cc < c + cs && cc < cols; cc++) newRowCovered[cc] = true;
1061
+ }
1062
+ }
1063
+ }
1064
+ for (let r = 0; r < P; r++) {
1065
+ for (let c = 0; c < cols; c++) {
1066
+ const idx = r * cols + c;
1067
+ const a = ctx.doc.getCellAttrs(blockIndex, idx);
1068
+ if (a.covered === true) continue;
1069
+ const rs = spanOf(a.rowspan);
1070
+ if (r < P && r + rs > P) {
1071
+ const m = ctx.doc.getCellAttrsMap(blockIndex, idx);
1072
+ m?.set("rowspan", rs + 1);
1073
+ }
1074
+ }
1075
+ }
1076
+ }
1077
+ const fresh = [];
1078
+ for (let c = 0; c < cols; c++) {
1079
+ fresh.push(createCell(newRowCovered[c] ? { covered: true } : void 0));
1080
+ }
1081
+ cells.insert(P * cols, fresh);
1082
+ attrs.set("rows", rows + 1);
1083
+ const firstReal = newRowCovered.findIndex((v) => !v);
1084
+ const targetCellIdx = P * cols + (firstReal < 0 ? 0 : firstReal);
1085
+ ctx.setSelection(
1086
+ collapsedSelection({ blockIndex, cellIndex: targetCellIdx, offset: 0 })
1087
+ );
1088
+ });
1089
+ }
1090
+ function deleteTableRow(ctx, blockIndex, row) {
1091
+ if (!ctx.doc.isTable(blockIndex)) return;
1092
+ const { rows, cols } = ctx.doc.getTableSize(blockIndex);
1093
+ if (rows <= 0 || cols <= 0) return;
1094
+ if (rows === 1) {
1095
+ deleteTable(ctx, blockIndex);
1096
+ return;
1097
+ }
1098
+ const R = Math.max(0, Math.min(row, rows - 1));
1099
+ transact(ctx.doc, () => {
1100
+ const cells = ctx.doc.getCells(blockIndex);
1101
+ const attrs = ctx.doc.getBlockAttrsMap(blockIndex);
1102
+ if (!cells || !attrs) return;
1103
+ for (let r = 0; r < R; r++) {
1104
+ for (let c = 0; c < cols; c++) {
1105
+ const idx = r * cols + c;
1106
+ const a = ctx.doc.getCellAttrs(blockIndex, idx);
1107
+ if (a.covered === true) continue;
1108
+ const rs = spanOf(a.rowspan);
1109
+ if (r < R && r + rs > R) {
1110
+ const m = ctx.doc.getCellAttrsMap(blockIndex, idx);
1111
+ m?.set("rowspan", rs - 1);
1112
+ if (rs - 1 === 1) m?.delete("rowspan");
1113
+ }
1114
+ }
1115
+ }
1116
+ const promotions = [];
1117
+ for (let c = 0; c < cols; c++) {
1118
+ const a = ctx.doc.getCellAttrs(blockIndex, R * cols + c);
1119
+ if (a.covered === true) continue;
1120
+ const rs = spanOf(a.rowspan);
1121
+ const cs = spanOf(a.colspan);
1122
+ if (rs > 1 && R + 1 < rows) {
1123
+ promotions.push({ flatNew: R * cols + c, rowspan: rs - 1, colspan: cs });
1124
+ }
1125
+ }
1126
+ cells.delete(R * cols, cols);
1127
+ attrs.set("rows", rows - 1);
1128
+ for (const p of promotions) {
1129
+ const m = ctx.doc.getCellAttrsMap(blockIndex, p.flatNew);
1130
+ if (!m) continue;
1131
+ m.delete("covered");
1132
+ if (p.rowspan > 1) m.set("rowspan", p.rowspan);
1133
+ else m.delete("rowspan");
1134
+ if (p.colspan > 1) m.set("colspan", p.colspan);
1135
+ else m.delete("colspan");
1136
+ }
1137
+ const newRow = Math.min(R, rows - 2);
1138
+ const target = findNextRealCell(ctx.doc, blockIndex, newRow * cols, (rows - 1) * cols) ?? 0;
1139
+ ctx.setSelection(
1140
+ collapsedSelection({ blockIndex, cellIndex: target, offset: 0 })
1141
+ );
1142
+ });
1143
+ }
1144
+ function insertTableColumn(ctx, blockIndex, col, where) {
1145
+ if (!ctx.doc.isTable(blockIndex)) return;
1146
+ const { rows, cols } = ctx.doc.getTableSize(blockIndex);
1147
+ if (rows <= 0 || cols <= 0) return;
1148
+ const targetCol = Math.max(0, Math.min(col, cols - 1));
1149
+ const C = where === "before" ? targetCol : targetCol + 1;
1150
+ transact(ctx.doc, () => {
1151
+ const cells = ctx.doc.getCells(blockIndex);
1152
+ const attrs = ctx.doc.getBlockAttrsMap(blockIndex);
1153
+ if (!cells || !attrs) return;
1154
+ const newColCovered = new Array(rows).fill(false);
1155
+ if (C > 0 && C < cols) {
1156
+ for (let r = 0; r < rows; r++) {
1157
+ for (let c = 0; c < C; c++) {
1158
+ const a = ctx.doc.getCellAttrs(blockIndex, r * cols + c);
1159
+ if (a.covered === true) continue;
1160
+ const rs = spanOf(a.rowspan);
1161
+ const cs = spanOf(a.colspan);
1162
+ if (c < C && c + cs > C) {
1163
+ for (let rr = r; rr < r + rs && rr < rows; rr++) newColCovered[rr] = true;
1164
+ }
1165
+ }
1166
+ }
1167
+ for (let r = 0; r < rows; r++) {
1168
+ for (let c = 0; c < C; c++) {
1169
+ const idx = r * cols + c;
1170
+ const a = ctx.doc.getCellAttrs(blockIndex, idx);
1171
+ if (a.covered === true) continue;
1172
+ const cs = spanOf(a.colspan);
1173
+ if (c < C && c + cs > C) {
1174
+ const m = ctx.doc.getCellAttrsMap(blockIndex, idx);
1175
+ m?.set("colspan", cs + 1);
1176
+ }
1177
+ }
1178
+ }
1179
+ }
1180
+ for (let r = rows - 1; r >= 0; r--) {
1181
+ const at = r * cols + C;
1182
+ cells.insert(at, [createCell(newColCovered[r] ? { covered: true } : void 0)]);
1183
+ }
1184
+ attrs.set("cols", cols + 1);
1185
+ const widths = attrs.get("colWidths");
1186
+ if (Array.isArray(widths) && widths.length === cols) {
1187
+ const next = widths.slice();
1188
+ next.splice(C, 0, 100);
1189
+ attrs.set("colWidths", next);
1190
+ }
1191
+ const newCols = cols + 1;
1192
+ const focusRow = 0;
1193
+ const firstReal = newColCovered.findIndex((_, r) => r === focusRow && !newColCovered[r]);
1194
+ ctx.setSelection(
1195
+ collapsedSelection({
1196
+ blockIndex,
1197
+ cellIndex: focusRow * newCols + C + (firstReal < 0 ? 0 : 0),
1198
+ offset: 0
1199
+ })
1200
+ );
1201
+ });
1202
+ }
1203
+ function deleteTableColumn(ctx, blockIndex, col) {
1204
+ if (!ctx.doc.isTable(blockIndex)) return;
1205
+ const { rows, cols } = ctx.doc.getTableSize(blockIndex);
1206
+ if (rows <= 0 || cols <= 0) return;
1207
+ if (cols === 1) {
1208
+ deleteTable(ctx, blockIndex);
1209
+ return;
1210
+ }
1211
+ const C = Math.max(0, Math.min(col, cols - 1));
1212
+ transact(ctx.doc, () => {
1213
+ const cells = ctx.doc.getCells(blockIndex);
1214
+ const attrs = ctx.doc.getBlockAttrsMap(blockIndex);
1215
+ if (!cells || !attrs) return;
1216
+ for (let r = 0; r < rows; r++) {
1217
+ for (let c = 0; c < C; c++) {
1218
+ const idx = r * cols + c;
1219
+ const a = ctx.doc.getCellAttrs(blockIndex, idx);
1220
+ if (a.covered === true) continue;
1221
+ const cs = spanOf(a.colspan);
1222
+ if (c < C && c + cs > C) {
1223
+ const m = ctx.doc.getCellAttrsMap(blockIndex, idx);
1224
+ m?.set("colspan", cs - 1);
1225
+ if (cs - 1 === 1) m?.delete("colspan");
1226
+ }
1227
+ }
1228
+ }
1229
+ const promotions = [];
1230
+ const newCols = cols - 1;
1231
+ for (let r = 0; r < rows; r++) {
1232
+ const a = ctx.doc.getCellAttrs(blockIndex, r * cols + C);
1233
+ if (a.covered === true) continue;
1234
+ const rs = spanOf(a.rowspan);
1235
+ const cs = spanOf(a.colspan);
1236
+ if (cs > 1 && C + 1 < cols) {
1237
+ promotions.push({
1238
+ flatNew: r * newCols + C,
1239
+ rowspan: rs,
1240
+ colspan: cs - 1
1241
+ });
1242
+ }
1243
+ }
1244
+ for (let r = rows - 1; r >= 0; r--) {
1245
+ const at = r * cols + C;
1246
+ cells.delete(at, 1);
1247
+ }
1248
+ attrs.set("cols", newCols);
1249
+ for (const p of promotions) {
1250
+ const m = ctx.doc.getCellAttrsMap(blockIndex, p.flatNew);
1251
+ if (!m) continue;
1252
+ m.delete("covered");
1253
+ if (p.rowspan > 1) m.set("rowspan", p.rowspan);
1254
+ else m.delete("rowspan");
1255
+ if (p.colspan > 1) m.set("colspan", p.colspan);
1256
+ else m.delete("colspan");
1257
+ }
1258
+ const widths = attrs.get("colWidths");
1259
+ if (Array.isArray(widths) && widths.length === cols) {
1260
+ const next = widths.slice();
1261
+ next.splice(C, 1);
1262
+ attrs.set("colWidths", next);
1263
+ }
1264
+ const newCol = Math.min(C, newCols - 1);
1265
+ const target = findNextRealCell(ctx.doc, blockIndex, newCol, rows * newCols) ?? 0;
1266
+ ctx.setSelection(
1267
+ collapsedSelection({ blockIndex, cellIndex: target, offset: 0 })
1268
+ );
1269
+ });
1270
+ }
1271
+ function tableRectSelection(doc, sel) {
1272
+ if (sel.anchor.blockIndex !== sel.focus.blockIndex) return null;
1273
+ if (sel.anchor.cellIndex == null || sel.focus.cellIndex == null) return null;
1274
+ const blockIndex = sel.anchor.blockIndex;
1275
+ if (!doc.isTable(blockIndex)) return null;
1276
+ const aLoc = tableLocationOf(doc, sel.anchor);
1277
+ const fLoc = tableLocationOf(doc, sel.focus);
1278
+ if (!aLoc || !fLoc) return null;
1279
+ if (aLoc.row === fLoc.row && aLoc.col === fLoc.col) return null;
1280
+ return expandTableRect(
1281
+ doc,
1282
+ blockIndex,
1283
+ Math.min(aLoc.row, fLoc.row),
1284
+ Math.min(aLoc.col, fLoc.col),
1285
+ Math.max(aLoc.row, fLoc.row),
1286
+ Math.max(aLoc.col, fLoc.col)
1287
+ );
1288
+ }
1289
+ function expandTableRect(doc, blockIndex, initTop, initLeft, initBottom, initRight) {
1290
+ const { rows, cols } = doc.getTableSize(blockIndex);
1291
+ let top = Math.max(0, initTop);
1292
+ let left = Math.max(0, initLeft);
1293
+ let bottom = Math.min(rows - 1, initBottom);
1294
+ let right = Math.min(cols - 1, initRight);
1295
+ let changed = true;
1296
+ let guard = rows * cols + 1;
1297
+ while (changed && guard-- > 0) {
1298
+ changed = false;
1299
+ for (let r = top; r <= bottom; r++) {
1300
+ for (let c = left; c <= right; c++) {
1301
+ const idx = r * cols + c;
1302
+ const a = doc.getCellAttrs(blockIndex, idx);
1303
+ if (a.covered === true) {
1304
+ const owner = doc.realCellIndex(blockIndex, idx);
1305
+ if (owner == null) continue;
1306
+ const oRow = Math.floor(owner / cols);
1307
+ const oCol = owner - oRow * cols;
1308
+ if (oRow < top) {
1309
+ top = oRow;
1310
+ changed = true;
1311
+ }
1312
+ if (oCol < left) {
1313
+ left = oCol;
1314
+ changed = true;
1315
+ }
1316
+ const oAttrs = doc.getCellAttrs(blockIndex, owner);
1317
+ const oRs = spanOf(oAttrs.rowspan);
1318
+ const oCs = spanOf(oAttrs.colspan);
1319
+ if (oRow + oRs - 1 > bottom) {
1320
+ bottom = oRow + oRs - 1;
1321
+ changed = true;
1322
+ }
1323
+ if (oCol + oCs - 1 > right) {
1324
+ right = oCol + oCs - 1;
1325
+ changed = true;
1326
+ }
1327
+ } else {
1328
+ const rs = spanOf(a.rowspan);
1329
+ const cs = spanOf(a.colspan);
1330
+ if (r + rs - 1 > bottom) {
1331
+ bottom = r + rs - 1;
1332
+ changed = true;
1333
+ }
1334
+ if (c + cs - 1 > right) {
1335
+ right = c + cs - 1;
1336
+ changed = true;
1337
+ }
1338
+ }
1339
+ }
1340
+ }
1341
+ }
1342
+ return { blockIndex, top, left, bottom, right };
1343
+ }
1344
+ function cellsInRect(doc, rect) {
1345
+ const { cols } = doc.getTableSize(rect.blockIndex);
1346
+ const out = [];
1347
+ for (let r = rect.top; r <= rect.bottom; r++) {
1348
+ for (let c = rect.left; c <= rect.right; c++) out.push(r * cols + c);
1349
+ }
1350
+ return out;
1351
+ }
1352
+ function mergeSelection(ctx) {
1353
+ const sel = ctx.getSelection();
1354
+ const rect = tableRectSelection(ctx.doc, sel);
1355
+ if (!rect) return false;
1356
+ const { blockIndex, top, left, bottom, right } = rect;
1357
+ const { cols } = ctx.doc.getTableSize(blockIndex);
1358
+ const ownerIdx = top * cols + left;
1359
+ transact(ctx.doc, () => {
1360
+ const ownerText = ctx.doc.getCellText(blockIndex, ownerIdx);
1361
+ if (!ownerText) return;
1362
+ for (let r = top; r <= bottom; r++) {
1363
+ for (let c = left; c <= right; c++) {
1364
+ const idx = r * cols + c;
1365
+ if (idx === ownerIdx) continue;
1366
+ const a = ctx.doc.getCellAttrs(blockIndex, idx);
1367
+ if (a.covered !== true) {
1368
+ const t = ctx.doc.getCellText(blockIndex, idx);
1369
+ if (t && t.length > 0) {
1370
+ const sep = ownerText.length > 0 ? " " : "";
1371
+ const delta = t.toDelta();
1372
+ let pos = ownerText.length;
1373
+ if (sep) {
1374
+ ownerText.insert(pos, sep);
1375
+ pos += 1;
1376
+ }
1377
+ writeDeltaInto(ownerText, pos, delta);
1378
+ t.delete(0, t.length);
1379
+ }
1380
+ }
1381
+ const m = ctx.doc.getCellAttrsMap(blockIndex, idx);
1382
+ if (m) {
1383
+ m.delete("rowspan");
1384
+ m.delete("colspan");
1385
+ m.set("covered", true);
1386
+ }
1387
+ }
1388
+ }
1389
+ const oMap = ctx.doc.getCellAttrsMap(blockIndex, ownerIdx);
1390
+ if (oMap) {
1391
+ const newRs = bottom - top + 1;
1392
+ const newCs = right - left + 1;
1393
+ if (newRs > 1) oMap.set("rowspan", newRs);
1394
+ else oMap.delete("rowspan");
1395
+ if (newCs > 1) oMap.set("colspan", newCs);
1396
+ else oMap.delete("colspan");
1397
+ oMap.delete("covered");
1398
+ }
1399
+ ctx.setSelection(
1400
+ collapsedSelection({ blockIndex, cellIndex: ownerIdx, offset: 0 })
1401
+ );
1402
+ });
1403
+ return true;
1404
+ }
1405
+ function clearRectCells(ctx) {
1406
+ const sel = ctx.getSelection();
1407
+ const rect = tableRectSelection(ctx.doc, sel);
1408
+ if (!rect) return null;
1409
+ const { blockIndex, top, left, bottom, right } = rect;
1410
+ const { cols } = ctx.doc.getTableSize(blockIndex);
1411
+ const ownerIdx = top * cols + left;
1412
+ let result = null;
1413
+ transact(ctx.doc, () => {
1414
+ for (let r = top; r <= bottom; r++) {
1415
+ for (let c = left; c <= right; c++) {
1416
+ const idx = r * cols + c;
1417
+ const a = ctx.doc.getCellAttrs(blockIndex, idx);
1418
+ if (a.covered === true) continue;
1419
+ const t = ctx.doc.getCellText(blockIndex, idx);
1420
+ if (t && t.length > 0) t.delete(0, t.length);
1421
+ }
1422
+ }
1423
+ result = collapsedSelection({ blockIndex, cellIndex: ownerIdx, offset: 0 });
1424
+ ctx.setSelection(result);
1425
+ });
1426
+ return result;
1427
+ }
1428
+ function mergeRight(ctx, blockIndex, row, col) {
1429
+ if (!ctx.doc.isTable(blockIndex)) return false;
1430
+ const { rows, cols } = ctx.doc.getTableSize(blockIndex);
1431
+ if (row < 0 || row >= rows || col < 0 || col >= cols) return false;
1432
+ const ownerIdx = row * cols + col;
1433
+ const ownerAttrs = ctx.doc.getCellAttrs(blockIndex, ownerIdx);
1434
+ if (ownerAttrs.covered === true) return false;
1435
+ const ownerRs = spanOf(ownerAttrs.rowspan);
1436
+ const ownerCs = spanOf(ownerAttrs.colspan);
1437
+ const nbCol = col + ownerCs;
1438
+ if (nbCol >= cols) return false;
1439
+ const nbIdx = row * cols + nbCol;
1440
+ const nbAttrs = ctx.doc.getCellAttrs(blockIndex, nbIdx);
1441
+ if (nbAttrs.covered === true) return false;
1442
+ const nbRs = spanOf(nbAttrs.rowspan);
1443
+ const nbCs = spanOf(nbAttrs.colspan);
1444
+ if (nbRs !== ownerRs) return false;
1445
+ transact(ctx.doc, () => {
1446
+ const ownerText = ctx.doc.getCellText(blockIndex, ownerIdx);
1447
+ const nbText = ctx.doc.getCellText(blockIndex, nbIdx);
1448
+ if (ownerText && nbText && nbText.length > 0) {
1449
+ const sep = ownerText.length > 0 ? " " : "";
1450
+ const delta = nbText.toDelta();
1451
+ let pos = ownerText.length;
1452
+ if (sep) {
1453
+ ownerText.insert(pos, sep);
1454
+ pos += 1;
1455
+ }
1456
+ writeDeltaInto(ownerText, pos, delta);
1457
+ }
1458
+ if (nbText && nbText.length > 0) nbText.delete(0, nbText.length);
1459
+ const ownerMap = ctx.doc.getCellAttrsMap(blockIndex, ownerIdx);
1460
+ if (ownerMap) ownerMap.set("colspan", ownerCs + nbCs);
1461
+ for (let r = row; r < row + ownerRs; r++) {
1462
+ for (let c = nbCol; c < nbCol + nbCs; c++) {
1463
+ const m = ctx.doc.getCellAttrsMap(blockIndex, r * cols + c);
1464
+ if (!m) continue;
1465
+ m.delete("rowspan");
1466
+ m.delete("colspan");
1467
+ m.set("covered", true);
1468
+ }
1469
+ }
1470
+ ctx.setSelection(collapsedSelection({ blockIndex, cellIndex: ownerIdx, offset: 0 }));
1471
+ });
1472
+ return true;
1473
+ }
1474
+ function mergeDown(ctx, blockIndex, row, col) {
1475
+ if (!ctx.doc.isTable(blockIndex)) return false;
1476
+ const { rows, cols } = ctx.doc.getTableSize(blockIndex);
1477
+ if (row < 0 || row >= rows || col < 0 || col >= cols) return false;
1478
+ const ownerIdx = row * cols + col;
1479
+ const ownerAttrs = ctx.doc.getCellAttrs(blockIndex, ownerIdx);
1480
+ if (ownerAttrs.covered === true) return false;
1481
+ const ownerRs = spanOf(ownerAttrs.rowspan);
1482
+ const ownerCs = spanOf(ownerAttrs.colspan);
1483
+ const nbRow = row + ownerRs;
1484
+ if (nbRow >= rows) return false;
1485
+ const nbIdx = nbRow * cols + col;
1486
+ const nbAttrs = ctx.doc.getCellAttrs(blockIndex, nbIdx);
1487
+ if (nbAttrs.covered === true) return false;
1488
+ const nbRs = spanOf(nbAttrs.rowspan);
1489
+ const nbCs = spanOf(nbAttrs.colspan);
1490
+ if (nbCs !== ownerCs) return false;
1491
+ transact(ctx.doc, () => {
1492
+ const ownerText = ctx.doc.getCellText(blockIndex, ownerIdx);
1493
+ const nbText = ctx.doc.getCellText(blockIndex, nbIdx);
1494
+ if (ownerText && nbText && nbText.length > 0) {
1495
+ const sep = ownerText.length > 0 ? " " : "";
1496
+ const delta = nbText.toDelta();
1497
+ let pos = ownerText.length;
1498
+ if (sep) {
1499
+ ownerText.insert(pos, sep);
1500
+ pos += 1;
1501
+ }
1502
+ writeDeltaInto(ownerText, pos, delta);
1503
+ }
1504
+ if (nbText && nbText.length > 0) nbText.delete(0, nbText.length);
1505
+ const ownerMap = ctx.doc.getCellAttrsMap(blockIndex, ownerIdx);
1506
+ if (ownerMap) ownerMap.set("rowspan", ownerRs + nbRs);
1507
+ for (let r = nbRow; r < nbRow + nbRs; r++) {
1508
+ for (let c = col; c < col + ownerCs; c++) {
1509
+ const m = ctx.doc.getCellAttrsMap(blockIndex, r * cols + c);
1510
+ if (!m) continue;
1511
+ m.delete("rowspan");
1512
+ m.delete("colspan");
1513
+ m.set("covered", true);
1514
+ }
1515
+ }
1516
+ ctx.setSelection(collapsedSelection({ blockIndex, cellIndex: ownerIdx, offset: 0 }));
1517
+ });
1518
+ return true;
1519
+ }
1520
+ function splitCell(ctx, blockIndex, row, col) {
1521
+ if (!ctx.doc.isTable(blockIndex)) return false;
1522
+ const { rows, cols } = ctx.doc.getTableSize(blockIndex);
1523
+ if (row < 0 || row >= rows || col < 0 || col >= cols) return false;
1524
+ const idx = row * cols + col;
1525
+ const attrs = ctx.doc.getCellAttrs(blockIndex, idx);
1526
+ if (attrs.covered === true) return false;
1527
+ const rs = spanOf(attrs.rowspan);
1528
+ const cs = spanOf(attrs.colspan);
1529
+ if (rs === 1 && cs === 1) return false;
1530
+ transact(ctx.doc, () => {
1531
+ const ownerMap = ctx.doc.getCellAttrsMap(blockIndex, idx);
1532
+ if (ownerMap) {
1533
+ ownerMap.delete("rowspan");
1534
+ ownerMap.delete("colspan");
1535
+ }
1536
+ for (let r = row; r < row + rs; r++) {
1537
+ for (let c = col; c < col + cs; c++) {
1538
+ if (r === row && c === col) continue;
1539
+ const m = ctx.doc.getCellAttrsMap(blockIndex, r * cols + c);
1540
+ if (!m) continue;
1541
+ m.delete("covered");
1542
+ m.delete("rowspan");
1543
+ m.delete("colspan");
1544
+ }
1545
+ }
1546
+ ctx.setSelection(collapsedSelection({ blockIndex, cellIndex: idx, offset: 0 }));
1547
+ });
1548
+ return true;
1549
+ }
1550
+ function setColumnWidth(ctx, blockIndex, col, widthPx) {
1551
+ if (!ctx.doc.isTable(blockIndex)) return;
1552
+ const { cols } = ctx.doc.getTableSize(blockIndex);
1553
+ if (col < 0 || col >= cols) return;
1554
+ const w = Math.max(20, Math.round(widthPx));
1555
+ transact(ctx.doc, () => {
1556
+ const attrsMap = ctx.doc.getBlockAttrsMap(blockIndex);
1557
+ if (!attrsMap) return;
1558
+ const current = attrsMap.get("colWidths");
1559
+ const next = Array.isArray(current) && current.length === cols ? current.slice() : new Array(cols).fill(120);
1560
+ next[col] = w;
1561
+ attrsMap.set("colWidths", next);
1562
+ });
1563
+ }
1564
+ function moveToNextCell(ctx) {
1565
+ const sel = ctx.getSelection();
1566
+ const loc = tableLocationOf(ctx.doc, sel.focus);
1567
+ if (!loc) return false;
1568
+ const { rows, cols } = ctx.doc.getTableSize(loc.blockIndex);
1569
+ if (rows <= 0 || cols <= 0) return false;
1570
+ const startFlat = loc.row * cols + loc.col + 1;
1571
+ const next = findNextRealCell(ctx.doc, loc.blockIndex, startFlat, rows * cols);
1572
+ if (next == null) {
1573
+ insertTableRow(ctx, loc.blockIndex, rows - 1, "after");
1574
+ return true;
1575
+ }
1576
+ ctx.setSelection(
1577
+ collapsedSelection({ blockIndex: loc.blockIndex, cellIndex: next, offset: 0 })
1578
+ );
1579
+ return true;
1580
+ }
1581
+ function moveToPrevCell(ctx) {
1582
+ const sel = ctx.getSelection();
1583
+ const loc = tableLocationOf(ctx.doc, sel.focus);
1584
+ if (!loc) return false;
1585
+ const { rows, cols } = ctx.doc.getTableSize(loc.blockIndex);
1586
+ if (rows <= 0 || cols <= 0) return false;
1587
+ const startFlat = loc.row * cols + loc.col - 1;
1588
+ if (startFlat < 0) return true;
1589
+ const prev = findPrevRealCell(ctx.doc, loc.blockIndex, startFlat);
1590
+ if (prev == null) return true;
1591
+ const cellText = ctx.doc.getCellText(loc.blockIndex, prev);
1592
+ const off = cellText?.length ?? 0;
1593
+ ctx.setSelection(
1594
+ collapsedSelection({ blockIndex: loc.blockIndex, cellIndex: prev, offset: off })
1595
+ );
1596
+ return true;
1597
+ }
1598
+ function findNextRealCell(doc, blockIndex, startFlat, total) {
1599
+ for (let i = startFlat; i < total; i++) {
1600
+ if (doc.getCellAttrs(blockIndex, i).covered !== true) return i;
1601
+ }
1602
+ return null;
1603
+ }
1604
+ function findPrevRealCell(doc, blockIndex, startFlat) {
1605
+ for (let i = startFlat; i >= 0; i--) {
1606
+ if (doc.getCellAttrs(blockIndex, i).covered !== true) return i;
1607
+ }
1608
+ return null;
1609
+ }
1610
+ function writeDeltaInto(yText, pos, delta) {
1611
+ let cursor = pos;
1612
+ for (const op of delta) {
1613
+ if (typeof op.insert === "string") {
1614
+ if (op.insert.length === 0) continue;
1615
+ if (op.attributes && Object.keys(op.attributes).length > 0) {
1616
+ yText.insert(cursor, op.insert, op.attributes);
1617
+ } else {
1618
+ yText.insert(cursor, op.insert);
1619
+ }
1620
+ cursor += op.insert.length;
1621
+ } else if (op.insert != null) {
1622
+ if (op.attributes && Object.keys(op.attributes).length > 0) {
1623
+ yText.insert(
1624
+ cursor,
1625
+ op.insert,
1626
+ op.attributes
1627
+ );
1628
+ } else {
1629
+ yText.insert(cursor, op.insert);
1630
+ }
1631
+ cursor += 1;
1632
+ }
1633
+ }
1634
+ return cursor;
1635
+ }
1636
+ function insertImage(ctx, embed) {
1637
+ clearRectCells(ctx);
1638
+ transact(ctx.doc, () => {
1639
+ const sel0 = ctx.getSelection();
1640
+ let cursor = sel0;
1641
+ if (!isCollapsed(sel0)) cursor = deleteRange(ctx.doc, sel0);
1642
+ const pos = cursor.focus;
1643
+ const yText = ctx.doc.textAt(pos.blockIndex, pos.cellIndex);
1644
+ if (!yText) return;
1645
+ yText.insert(pos.offset, embed);
1646
+ const newPos = {
1647
+ blockIndex: pos.blockIndex,
1648
+ cellIndex: pos.cellIndex,
1649
+ offset: pos.offset + 1
1650
+ };
1651
+ ctx.setSelection(collapsedSelection(newPos));
1652
+ });
1653
+ }
1654
+ function setImageAttrs(ctx, blockIndex, embedOffset, partial, cellIndex, newOffset) {
1655
+ transact(ctx.doc, () => {
1656
+ const yText = ctx.doc.textAt(blockIndex, cellIndex);
1657
+ if (!yText) return;
1658
+ const delta = yText.toDelta();
1659
+ let cursor = 0;
1660
+ let prev = null;
1661
+ for (const op of delta) {
1662
+ if (typeof op.insert === "string") {
1663
+ cursor += op.insert.length;
1664
+ continue;
1665
+ }
1666
+ if (cursor === embedOffset && isImageEmbed(op.insert)) {
1667
+ prev = op.insert;
1668
+ break;
1669
+ }
1670
+ cursor += 1;
1671
+ }
1672
+ if (!prev) return;
1673
+ const merged = { ...prev, ...partial };
1674
+ yText.delete(embedOffset, 1);
1675
+ const insertAt = newOffset != null && newOffset !== embedOffset ? Math.max(0, Math.min(newOffset, yText.length)) : embedOffset;
1676
+ yText.insert(insertAt, merged);
1677
+ });
1678
+ }
1679
+ function isCommandOrigin(origin) {
1680
+ return origin === COMMAND_ORIGIN;
1681
+ }
1682
+ var TRACKED_ORIGINS = /* @__PURE__ */ new Set([COMMAND_ORIGIN]);
1683
+
1684
+ // src/history.ts
1685
+ import * as Y3 from "yjs";
1686
+ var EditorHistory = class {
1687
+ undoManager;
1688
+ constructor(doc) {
1689
+ this.undoManager = new Y3.UndoManager(doc.blocks, {
1690
+ trackedOrigins: TRACKED_ORIGINS,
1691
+ captureTimeout: 400
1692
+ });
1693
+ }
1694
+ undo() {
1695
+ return this.undoManager.undo() != null;
1696
+ }
1697
+ redo() {
1698
+ return this.undoManager.redo() != null;
1699
+ }
1700
+ destroy() {
1701
+ this.undoManager.destroy();
1702
+ }
1703
+ };
1704
+ export {
1705
+ BLOCK_SPECS,
1706
+ COMMAND_ORIGIN,
1707
+ DEFAULT_PAGE_SETTINGS,
1708
+ EditorDocument,
1709
+ EditorHistory,
1710
+ MARGIN_PRESETS,
1711
+ MAX_LIST_LEVEL,
1712
+ PAGE_PRESETS,
1713
+ TRACKED_ORIGINS,
1714
+ Y2 as Y,
1715
+ cellsInRect,
1716
+ clearRectCells,
1717
+ collapsedSelection,
1718
+ comparePositions,
1719
+ createBlock,
1720
+ createCell,
1721
+ createTableBlock,
1722
+ dedentList,
1723
+ defaultAttrsFor,
1724
+ deleteBackward,
1725
+ deleteForward,
1726
+ deleteTable,
1727
+ deleteTableColumn,
1728
+ deleteTableRow,
1729
+ deltaLength,
1730
+ detectPreset,
1731
+ expandTableRect,
1732
+ getMarksInRange,
1733
+ indentList,
1734
+ insertImage,
1735
+ insertParagraph,
1736
+ insertTable,
1737
+ insertTableColumn,
1738
+ insertTableRow,
1739
+ insertText,
1740
+ isCollapsed,
1741
+ isCommandOrigin,
1742
+ isImageEmbed,
1743
+ isKnownBlockType,
1744
+ isLegacySerializedDocument,
1745
+ isMarkUniformInRange,
1746
+ mergeDown,
1747
+ mergeRight,
1748
+ mergeSelection,
1749
+ mmToPx,
1750
+ moveToNextCell,
1751
+ moveToPrevCell,
1752
+ orderedRange,
1753
+ positionsEqual,
1754
+ pxToMm,
1755
+ removeMark,
1756
+ sameTextRun,
1757
+ setBlockAttr,
1758
+ setBlockType,
1759
+ setColumnWidth,
1760
+ setImageAttrs,
1761
+ setMark,
1762
+ sliceDelta,
1763
+ spanOf,
1764
+ splitCell,
1765
+ splitListItem,
1766
+ tableLocationOf,
1767
+ tableRectSelection,
1768
+ toggleList,
1769
+ toggleMark,
1770
+ withDefaults
1771
+ };
1772
+ //# sourceMappingURL=index.js.map