@ai-matrx/office 0.7.0 → 0.8.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/CHANGELOG.md +15 -0
- package/dist/docs.cjs +177 -28
- package/dist/docs.cjs.map +1 -1
- package/dist/docs.d.cts +83 -2
- package/dist/docs.d.ts +83 -2
- package/dist/docs.js +154 -5
- package/dist/docs.js.map +1 -1
- package/dist/univer-collab.cjs +1004 -1
- package/dist/univer-collab.cjs.map +1 -1
- package/dist/univer-collab.d.cts +252 -1
- package/dist/univer-collab.d.ts +252 -1
- package/dist/univer-collab.js +1010 -1
- package/dist/univer-collab.js.map +1 -1
- package/package.json +3 -2
package/dist/univer-collab.js
CHANGED
|
@@ -358,13 +358,1022 @@ function pickColor(uid) {
|
|
|
358
358
|
}
|
|
359
359
|
return CURSOR_PALETTE[Math.abs(h) % CURSOR_PALETTE.length] ?? "#ec4899";
|
|
360
360
|
}
|
|
361
|
+
|
|
362
|
+
// src/univer/collab/docs-ot/transform.ts
|
|
363
|
+
import { JSONX, TextX, getBodySliceForSplitTextXAction, normalizeTextRuns, merge as univerMerge } from "@univerjs/core";
|
|
364
|
+
import * as json1 from "ot-json1";
|
|
365
|
+
var isTextX = (c) => !!c && typeof c === "object" && c.et === "text-x" && Array.isArray(c.e);
|
|
366
|
+
var clone = (v) => v == null ? v : structuredClone(v);
|
|
367
|
+
function tableZones(a) {
|
|
368
|
+
const out = [];
|
|
369
|
+
for (const t of a.body?.tables ?? []) {
|
|
370
|
+
if (t.startIndex >= 0 && t.endIndex <= a.len && t.endIndex > t.startIndex) out.push([t.startIndex, t.endIndex - 1]);
|
|
371
|
+
}
|
|
372
|
+
return out;
|
|
373
|
+
}
|
|
374
|
+
function unrewind(actions) {
|
|
375
|
+
if (!actions.some((a) => a.len < 0)) return actions;
|
|
376
|
+
const segs = [];
|
|
377
|
+
let cur = [];
|
|
378
|
+
let cursor = 0;
|
|
379
|
+
for (const a of actions) {
|
|
380
|
+
if (a.len < 0) {
|
|
381
|
+
if (a.t !== "r") return actions;
|
|
382
|
+
segs.push(cur);
|
|
383
|
+
cursor += a.len;
|
|
384
|
+
cur = cursor > 0 ? [{ t: "r", len: cursor }] : [];
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
cur.push(a);
|
|
388
|
+
if (a.t !== "d") cursor += a.len;
|
|
389
|
+
}
|
|
390
|
+
segs.push(cur);
|
|
391
|
+
const out = segs.reduce((acc, s) => TextX.compose(acc, s));
|
|
392
|
+
return out.some((a) => a.len < 0) ? actions : out;
|
|
393
|
+
}
|
|
394
|
+
function normText(actions) {
|
|
395
|
+
const out = [];
|
|
396
|
+
for (const a of unrewind(actions)) {
|
|
397
|
+
if (a.t === "d") {
|
|
398
|
+
if (!(a.len > 0)) continue;
|
|
399
|
+
const d = { t: "d", len: a.len };
|
|
400
|
+
if (a.valueRangeId !== void 0) d.valueRangeId = a.valueRangeId;
|
|
401
|
+
const tz = a.tz ?? tableZones(a);
|
|
402
|
+
if (tz.length) d.tz = tz;
|
|
403
|
+
out.push(d);
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
if (a.t === "r") {
|
|
407
|
+
const { oldBody: _oldBody, ...rest } = a;
|
|
408
|
+
out.push(rest);
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
out.push(a);
|
|
412
|
+
}
|
|
413
|
+
return out;
|
|
414
|
+
}
|
|
415
|
+
function normalizeDocsOp(op) {
|
|
416
|
+
if (op == null) return op;
|
|
417
|
+
const walk = (v) => {
|
|
418
|
+
if (Array.isArray(v)) return v.map(walk);
|
|
419
|
+
if (isTextX(v)) return { ...v, e: normText(v.e) };
|
|
420
|
+
return v;
|
|
421
|
+
};
|
|
422
|
+
return walk(op);
|
|
423
|
+
}
|
|
424
|
+
function dropEmptyStyleRetains(op) {
|
|
425
|
+
if (op == null) return op;
|
|
426
|
+
const walk = (v) => {
|
|
427
|
+
if (Array.isArray(v)) return v.map(walk);
|
|
428
|
+
if (isTextX(v)) {
|
|
429
|
+
return {
|
|
430
|
+
...v,
|
|
431
|
+
e: v.e.map((a) => {
|
|
432
|
+
const b = a.body;
|
|
433
|
+
if (a.t !== "r" || !b || a.coverType === 1) return a;
|
|
434
|
+
const runs = (b.textRuns ?? []).filter((r) => r.ts && Object.keys(r.ts).length);
|
|
435
|
+
const other = Object.keys(b).filter(
|
|
436
|
+
(k) => k !== "textRuns" && k !== "dataStream" && !(Array.isArray(b[k]) && b[k].length === 0)
|
|
437
|
+
);
|
|
438
|
+
if (!runs.length && !other.length && !b.dataStream && !a.rangeUpdates?.length) return { t: "r", len: a.len };
|
|
439
|
+
if (b.textRuns === void 0 || runs.length === b.textRuns.length) return a;
|
|
440
|
+
return { ...a, body: { ...b, textRuns: runs } };
|
|
441
|
+
})
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
return v;
|
|
445
|
+
};
|
|
446
|
+
return walk(op);
|
|
447
|
+
}
|
|
448
|
+
var onlyDataStream = (body) => Object.keys(body).length === 1;
|
|
449
|
+
var shiftZones = (tz, by) => tz.map(([s, e]) => [s + by, e + by]);
|
|
450
|
+
function mergeZones(tz) {
|
|
451
|
+
const s = [...tz].sort((x, y) => x[0] - y[0]);
|
|
452
|
+
const out = [];
|
|
453
|
+
for (const z of s) {
|
|
454
|
+
const l = out[out.length - 1];
|
|
455
|
+
if (l && l[1] + 1 >= z[0]) l[1] = Math.max(l[1], z[1]);
|
|
456
|
+
else out.push([z[0], z[1]]);
|
|
457
|
+
}
|
|
458
|
+
return out;
|
|
459
|
+
}
|
|
460
|
+
function push(list, action) {
|
|
461
|
+
let index = list.length;
|
|
462
|
+
let last = list[index - 1];
|
|
463
|
+
const a = clone(action);
|
|
464
|
+
if (a.t === "r" && a.len === 0 && a.body == null && !a.rangeUpdates) return;
|
|
465
|
+
if (last) {
|
|
466
|
+
if (last.t === "d" && a.t === "d" && last.valueRangeId === a.valueRangeId) {
|
|
467
|
+
const tz = [...last.tz ?? [], ...shiftZones(a.tz ?? [], last.len)];
|
|
468
|
+
last.len += a.len;
|
|
469
|
+
if (tz.length) last.tz = mergeZones(tz);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
if (last.t === "d" && a.t === "i") {
|
|
473
|
+
index -= 1;
|
|
474
|
+
last = list[index - 1];
|
|
475
|
+
if (last == null) {
|
|
476
|
+
list.unshift(a);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (last.t === "r" && a.t === "r" && last.body == null && a.body == null && !last.rangeUpdates && !a.rangeUpdates) {
|
|
481
|
+
last.len += a.len;
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (last.t === "i" && onlyDataStream(last.body) && a.t === "i" && onlyDataStream(a.body) && last.valueRangeId === a.valueRangeId) {
|
|
485
|
+
last.len += a.len;
|
|
486
|
+
last.body.dataStream += a.body.dataStream;
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (index === list.length) list.push(a);
|
|
491
|
+
else list.splice(index, 0, a);
|
|
492
|
+
}
|
|
493
|
+
var BODY_FIELDS = [
|
|
494
|
+
"textRuns",
|
|
495
|
+
"paragraphs",
|
|
496
|
+
"customRanges",
|
|
497
|
+
"customBlocks",
|
|
498
|
+
"docxRawCustomBlocks",
|
|
499
|
+
"docxRawBlocks",
|
|
500
|
+
"docxExportExcludedRanges",
|
|
501
|
+
"customDecorations",
|
|
502
|
+
"tables",
|
|
503
|
+
"columnGroups",
|
|
504
|
+
"blockRanges"
|
|
505
|
+
];
|
|
506
|
+
var uselessRetain = (a) => !a.rangeUpdates?.length && (a.body == null || BODY_FIELDS.every((k) => a.body[k] == null));
|
|
507
|
+
function trimEnd(list) {
|
|
508
|
+
while (list.length && list[list.length - 1].t === "r" && uselessRetain(list[list.length - 1])) list.pop();
|
|
509
|
+
return list;
|
|
510
|
+
}
|
|
511
|
+
var It = class {
|
|
512
|
+
constructor(a) {
|
|
513
|
+
this.a = a;
|
|
514
|
+
}
|
|
515
|
+
a;
|
|
516
|
+
i = 0;
|
|
517
|
+
off = 0;
|
|
518
|
+
peekType() {
|
|
519
|
+
return this.a[this.i]?.t ?? "r";
|
|
520
|
+
}
|
|
521
|
+
peekLength() {
|
|
522
|
+
const x = this.a[this.i];
|
|
523
|
+
return x ? x.len - this.off : Number.POSITIVE_INFINITY;
|
|
524
|
+
}
|
|
525
|
+
hasNext() {
|
|
526
|
+
return this.peekLength() < Number.POSITIVE_INFINITY;
|
|
527
|
+
}
|
|
528
|
+
/** An insert placed now would land strictly inside a table this delete removes whole. */
|
|
529
|
+
inZone() {
|
|
530
|
+
const x = this.a[this.i];
|
|
531
|
+
return x?.t === "d" && (x.tz ?? []).some(([s, e]) => s < this.off && this.off <= e);
|
|
532
|
+
}
|
|
533
|
+
next(length) {
|
|
534
|
+
let len = length || Number.POSITIVE_INFINITY;
|
|
535
|
+
const x = this.a[this.i];
|
|
536
|
+
if (!x) return { t: "r", len: Number.POSITIVE_INFINITY };
|
|
537
|
+
const offset = this.off;
|
|
538
|
+
if (len >= x.len - offset) {
|
|
539
|
+
len = x.len - offset;
|
|
540
|
+
this.i += 1;
|
|
541
|
+
this.off = 0;
|
|
542
|
+
} else this.off += len;
|
|
543
|
+
if (x.t === "d") {
|
|
544
|
+
const piece = { ...clone(x), len };
|
|
545
|
+
delete piece.tz;
|
|
546
|
+
const tz = (x.tz ?? []).map(([s, e]) => [Math.max(s, offset) - offset, Math.min(e, offset + len - 1) - offset]).filter(([s, e]) => s <= e);
|
|
547
|
+
if (tz.length) piece.tz = tz;
|
|
548
|
+
return piece;
|
|
549
|
+
}
|
|
550
|
+
if (x.t === "r" && x.body == null) return { ...clone(x), len };
|
|
551
|
+
return { ...clone(x), len, body: getBodySliceForSplitTextXAction(x.body, offset, offset + len, false) };
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
function transformTextRuns(originTextRuns, targetTextRuns, originCoverType, targetCoverType, transformType) {
|
|
555
|
+
if (originTextRuns == null || targetTextRuns == null) return targetTextRuns;
|
|
556
|
+
if (originTextRuns.length === 0 || targetTextRuns.length === 0) return [];
|
|
557
|
+
targetTextRuns = clone(targetTextRuns);
|
|
558
|
+
originTextRuns = clone(originTextRuns);
|
|
559
|
+
const out = [];
|
|
560
|
+
const updateLength = targetTextRuns.length;
|
|
561
|
+
const removeLength = originTextRuns.length;
|
|
562
|
+
let updateIndex = 0;
|
|
563
|
+
let removeIndex = 0;
|
|
564
|
+
let pending = null;
|
|
565
|
+
const flush = () => {
|
|
566
|
+
if (pending) {
|
|
567
|
+
out.push(pending);
|
|
568
|
+
pending = null;
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
return false;
|
|
572
|
+
};
|
|
573
|
+
while (updateIndex < updateLength && removeIndex < removeLength) {
|
|
574
|
+
const { st: updateSt, ed: updateEd, ts: targetStyle } = targetTextRuns[updateIndex];
|
|
575
|
+
const { st: removeSt, ed: removeEd, ts: originStyle, sId } = originTextRuns[removeIndex];
|
|
576
|
+
let newTs = {};
|
|
577
|
+
if (transformType === 0) {
|
|
578
|
+
newTs = { ...targetStyle };
|
|
579
|
+
if (originCoverType === 0 && targetCoverType === 1 && originStyle) newTs = Object.assign({}, originStyle, newTs);
|
|
580
|
+
} else {
|
|
581
|
+
newTs = { ...targetStyle };
|
|
582
|
+
if (originCoverType === 1) {
|
|
583
|
+
if (targetCoverType === 1) newTs = { ...originStyle };
|
|
584
|
+
else if (targetStyle && originStyle) {
|
|
585
|
+
for (const key of Object.keys(targetStyle)) if (originStyle[key] !== void 0) delete newTs[key];
|
|
586
|
+
}
|
|
587
|
+
} else if (targetCoverType === 1) {
|
|
588
|
+
if (originStyle) {
|
|
589
|
+
for (const key of Object.keys(originStyle)) if (originStyle[key] !== void 0) newTs[key] = originStyle[key];
|
|
590
|
+
}
|
|
591
|
+
} else if (originStyle) {
|
|
592
|
+
for (const key of Object.keys(originStyle)) if (newTs[key] !== void 0) delete newTs[key];
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (updateEd < removeSt) {
|
|
596
|
+
if (!flush()) out.push(targetTextRuns[updateIndex]);
|
|
597
|
+
updateIndex++;
|
|
598
|
+
} else if (removeEd < updateSt) {
|
|
599
|
+
if (!flush()) out.push(originTextRuns[removeIndex]);
|
|
600
|
+
removeIndex++;
|
|
601
|
+
} else {
|
|
602
|
+
out.push({ st: Math.max(updateSt, removeSt), ed: Math.min(updateEd, removeEd), ts: newTs, sId });
|
|
603
|
+
if (updateEd < removeEd) {
|
|
604
|
+
updateIndex++;
|
|
605
|
+
originTextRuns[removeIndex].st = updateEd;
|
|
606
|
+
if (originTextRuns[removeIndex].st === originTextRuns[removeIndex].ed) removeIndex++;
|
|
607
|
+
} else {
|
|
608
|
+
removeIndex++;
|
|
609
|
+
targetTextRuns[updateIndex].st = removeEd;
|
|
610
|
+
if (targetTextRuns[updateIndex].st === targetTextRuns[updateIndex].ed) updateIndex++;
|
|
611
|
+
}
|
|
612
|
+
const p = {
|
|
613
|
+
st: Math.min(updateEd, removeEd),
|
|
614
|
+
ed: Math.max(updateEd, removeEd),
|
|
615
|
+
ts: updateEd < removeEd ? { ...originStyle } : { ...targetStyle },
|
|
616
|
+
sId: updateEd < removeEd ? sId : void 0
|
|
617
|
+
};
|
|
618
|
+
pending = p.ed > p.st ? p : null;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
flush();
|
|
622
|
+
const top = out[out.length - 1];
|
|
623
|
+
const updateLast = targetTextRuns[updateLength - 1];
|
|
624
|
+
const removeLast = originTextRuns[removeLength - 1];
|
|
625
|
+
if (top.ed !== Math.max(updateLast.ed, removeLast.ed)) out.push(updateLast.ed > removeLast.ed ? updateLast : removeLast);
|
|
626
|
+
return normalizeTextRuns(out, true);
|
|
627
|
+
}
|
|
628
|
+
function transformCustomRanges(o, t, originCoverType, _targetCoverType, transformType) {
|
|
629
|
+
if (o == null || t == null) return t;
|
|
630
|
+
if (o.length === 0 || t.length === 0) return [];
|
|
631
|
+
if (o.length > 1 || t.length > 1) throw new Error("CustomRanges is only supported transform for length one now.");
|
|
632
|
+
if (originCoverType === 1) return transformType === 1 ? [clone(o[0])] : [clone(t[0])];
|
|
633
|
+
const r = clone(t[0]);
|
|
634
|
+
if (transformType === 1) Object.assign(r, clone(o[0]));
|
|
635
|
+
return [r];
|
|
636
|
+
}
|
|
637
|
+
function transformParagraph(origin, target, originCoverType, targetCoverType, transformType) {
|
|
638
|
+
const paragraph = { startIndex: target.startIndex, paragraphId: target.paragraphId };
|
|
639
|
+
const oStyle = originCoverType === 1 ? origin.paragraphStyle ?? {} : origin.paragraphStyle;
|
|
640
|
+
const tStyle = targetCoverType === 1 ? target.paragraphStyle ?? {} : target.paragraphStyle;
|
|
641
|
+
if (tStyle) {
|
|
642
|
+
paragraph.paragraphStyle = clone(tStyle);
|
|
643
|
+
if (oStyle) {
|
|
644
|
+
const ps = paragraph.paragraphStyle;
|
|
645
|
+
if (originCoverType === 1) {
|
|
646
|
+
if (targetCoverType === 1) {
|
|
647
|
+
if (transformType === 1) paragraph.paragraphStyle = { ...oStyle };
|
|
648
|
+
} else if (transformType === 1) {
|
|
649
|
+
for (const k of Object.keys(oStyle)) if (oStyle[k] !== void 0) ps[k] = oStyle[k];
|
|
650
|
+
} else for (const k of Object.keys(oStyle)) if (ps[k] === void 0) ps[k] = oStyle[k];
|
|
651
|
+
} else if (targetCoverType === 1) {
|
|
652
|
+
if (transformType === 1) {
|
|
653
|
+
for (const k of Object.keys(oStyle)) if (oStyle[k] !== void 0) ps[k] = oStyle[k];
|
|
654
|
+
} else for (const k of Object.keys(oStyle)) if (ps[k] === void 0) ps[k] = oStyle[k];
|
|
655
|
+
} else if (transformType === 1) {
|
|
656
|
+
for (const k of Object.keys(oStyle)) if (ps[k] !== void 0) delete ps[k];
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
if (targetCoverType !== 1 && Object.keys(paragraph.paragraphStyle).length === 0) delete paragraph.paragraphStyle;
|
|
660
|
+
}
|
|
661
|
+
if (originCoverType === 1 && targetCoverType === 1) paragraph.bullet = transformType === 1 ? clone(origin.bullet) : clone(target.bullet);
|
|
662
|
+
else if (origin.bullet === void 0) paragraph.bullet = clone(target.bullet);
|
|
663
|
+
else if (originCoverType === 1 || targetCoverType === 1) paragraph.bullet = transformType === 0 && target.bullet ? clone(target.bullet) : clone(origin.bullet);
|
|
664
|
+
else if (transformType === 0 && target.bullet !== void 0) paragraph.bullet = clone(target.bullet);
|
|
665
|
+
if (paragraph.bullet === void 0) delete paragraph.bullet;
|
|
666
|
+
if (paragraph.paragraphId === void 0) delete paragraph.paragraphId;
|
|
667
|
+
return paragraph;
|
|
668
|
+
}
|
|
669
|
+
function transformCustomDecorations(origin, target) {
|
|
670
|
+
if (origin == null || target == null) return target;
|
|
671
|
+
if (origin.length === 0 || target.length === 0) return clone(target);
|
|
672
|
+
const out = [];
|
|
673
|
+
for (const d of target) {
|
|
674
|
+
let pushed = false;
|
|
675
|
+
for (const o of origin) {
|
|
676
|
+
if (o.id === d.id) {
|
|
677
|
+
if (o.type === 9999 || d.type === 9999) {
|
|
678
|
+
pushed = true;
|
|
679
|
+
out.push({ ...d, type: 9999 });
|
|
680
|
+
}
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
if (!pushed) out.push(d);
|
|
685
|
+
}
|
|
686
|
+
return out;
|
|
687
|
+
}
|
|
688
|
+
function transformBlockRanges(thisBlockRanges, otherBlockRanges, priority) {
|
|
689
|
+
if (!thisBlockRanges.length) return otherBlockRanges;
|
|
690
|
+
if (!otherBlockRanges.length) return [];
|
|
691
|
+
return otherBlockRanges.map((o) => {
|
|
692
|
+
const t = thisBlockRanges.find((b) => b.blockId === o.blockId);
|
|
693
|
+
return t && priority ? univerMerge(o, t) : o;
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
function transformBody(thisAction, otherAction, priority) {
|
|
697
|
+
const { body: thisBody, coverType: thisCoverType = 0 } = thisAction;
|
|
698
|
+
const { body: otherBody, coverType: otherCoverType = 0 } = otherAction;
|
|
699
|
+
if (thisBody == null || thisBody.dataStream !== "" || otherBody == null || otherBody.dataStream !== "") {
|
|
700
|
+
throw new Error("Data stream is not supported in retain transform.");
|
|
701
|
+
}
|
|
702
|
+
const ret = { dataStream: "" };
|
|
703
|
+
const { textRuns: tr1, paragraphs: p1 = [], blockRanges: b1 = [], customRanges: c1, customDecorations: d1 } = thisBody;
|
|
704
|
+
const { textRuns: tr2, paragraphs: p2 = [], blockRanges: b2 = [], customRanges: c2, customDecorations: d2 } = otherBody;
|
|
705
|
+
const pt = priority ? 1 : 0;
|
|
706
|
+
const textRuns = transformTextRuns(tr1, tr2, thisCoverType, otherCoverType, pt);
|
|
707
|
+
if (textRuns) ret.textRuns = textRuns;
|
|
708
|
+
const customRanges = transformCustomRanges(c1, c2, thisCoverType, otherCoverType, pt);
|
|
709
|
+
if (customRanges) ret.customRanges = customRanges;
|
|
710
|
+
const customDecorations = transformCustomDecorations(d1, d2);
|
|
711
|
+
if (customDecorations) ret.customDecorations = customDecorations;
|
|
712
|
+
const paragraphs = [];
|
|
713
|
+
let i = 0;
|
|
714
|
+
let j = 0;
|
|
715
|
+
while (i < p1.length && j < p2.length) {
|
|
716
|
+
const a = p1[i];
|
|
717
|
+
const b = p2[j];
|
|
718
|
+
if (a.startIndex === b.startIndex) {
|
|
719
|
+
paragraphs.push(transformParagraph(a, b, thisCoverType, otherCoverType, pt));
|
|
720
|
+
i++;
|
|
721
|
+
j++;
|
|
722
|
+
} else if (a.startIndex < b.startIndex) i++;
|
|
723
|
+
else {
|
|
724
|
+
paragraphs.push(clone(b));
|
|
725
|
+
j++;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
if (j < p2.length) paragraphs.push(...p2.slice(j));
|
|
729
|
+
if (paragraphs.length) ret.paragraphs = paragraphs;
|
|
730
|
+
const blockRanges = transformBlockRanges(b1, b2, priority);
|
|
731
|
+
if (blockRanges.length) ret.blockRanges = blockRanges;
|
|
732
|
+
return { coverType: otherCoverType, body: ret };
|
|
733
|
+
}
|
|
734
|
+
var valueIds = (acts) => new Set(acts.flatMap((a) => a.t !== "r" && a.valueRangeId ? [a.valueRangeId] : []));
|
|
735
|
+
function withoutValueRangeMetadata(acts, suppressed) {
|
|
736
|
+
return acts.map((a) => {
|
|
737
|
+
if (!a.body?.customRanges?.some((r) => suppressed.has(r.rangeId))) return a;
|
|
738
|
+
const body = { ...a.body, customRanges: a.body.customRanges.filter((r) => !suppressed.has(r.rangeId)) };
|
|
739
|
+
if (!body.customRanges.length) delete body.customRanges;
|
|
740
|
+
return { ...a, body };
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
function addedDecorations(y) {
|
|
744
|
+
const out = [];
|
|
745
|
+
let pos = 0;
|
|
746
|
+
for (const a of y) {
|
|
747
|
+
if (a.t === "d") continue;
|
|
748
|
+
if (a.t === "r") {
|
|
749
|
+
for (const d of a.body?.customDecorations ?? []) if (d.type !== 9999) out.push({ id: d.id, type: d.type, ds: pos + d.startIndex, de: pos + d.endIndex });
|
|
750
|
+
}
|
|
751
|
+
pos += a.len;
|
|
752
|
+
}
|
|
753
|
+
return out;
|
|
754
|
+
}
|
|
755
|
+
var hasRangeUpdates = (acts) => acts.some((a) => a.t === "r" && a.rangeUpdates?.length);
|
|
756
|
+
function transformTextX(xIn, yIn, side, lost = []) {
|
|
757
|
+
if (hasRangeUpdates(xIn) || hasRangeUpdates(yIn)) return TextX.transform(xIn, yIn, side);
|
|
758
|
+
const x = normText(xIn);
|
|
759
|
+
const y = normText(yIn);
|
|
760
|
+
const xLeft = side === "left";
|
|
761
|
+
const yIds = valueIds(y);
|
|
762
|
+
const conflicts = new Set([...valueIds(x)].filter((id) => yIds.has(id)));
|
|
763
|
+
const X = new It(conflicts.size && !xLeft ? withoutValueRangeMetadata(x, conflicts) : x);
|
|
764
|
+
const Y3 = new It(conflicts.size && xLeft ? withoutValueRangeMetadata(y, conflicts) : y);
|
|
765
|
+
const decos = addedDecorations(y).map((d) => ({ ...d, pieces: [] }));
|
|
766
|
+
const out = [];
|
|
767
|
+
let inPos = 0;
|
|
768
|
+
let outPos = 0;
|
|
769
|
+
while (X.hasNext() || Y3.hasNext()) {
|
|
770
|
+
if (X.peekType() === "i" && (xLeft || Y3.peekType() !== "i")) {
|
|
771
|
+
const a = X.next();
|
|
772
|
+
if (Y3.inZone()) {
|
|
773
|
+
lost.push({ rule: "R1", side: "insert", len: a.len, ...a.body?.dataStream !== void 0 ? { text: a.body.dataStream } : {} });
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
if (a.valueRangeId && conflicts.has(a.valueRangeId) && !xLeft) continue;
|
|
777
|
+
push(out, a);
|
|
778
|
+
outPos += a.len;
|
|
779
|
+
} else if (Y3.peekType() === "i") {
|
|
780
|
+
const b = Y3.next();
|
|
781
|
+
if (X.inZone()) {
|
|
782
|
+
push(out, { t: "d", len: b.len, tz: [[0, b.len - 1]] });
|
|
783
|
+
lost.push({ rule: "R1", side: "delete", len: b.len, ...b.body?.dataStream !== void 0 ? { text: b.body.dataStream } : {} });
|
|
784
|
+
inPos += b.len;
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
787
|
+
if (b.valueRangeId && conflicts.has(b.valueRangeId) && xLeft) push(out, { t: "d", len: b.len, valueRangeId: b.valueRangeId });
|
|
788
|
+
else {
|
|
789
|
+
push(out, { t: "r", len: b.len });
|
|
790
|
+
outPos += b.len;
|
|
791
|
+
}
|
|
792
|
+
inPos += b.len;
|
|
793
|
+
} else {
|
|
794
|
+
const len = Math.min(X.peekLength(), Y3.peekLength());
|
|
795
|
+
const a = X.next(len);
|
|
796
|
+
const b = Y3.next(len);
|
|
797
|
+
if (b.t === "d") continue;
|
|
798
|
+
if (a.t === "d") {
|
|
799
|
+
push(out, a);
|
|
800
|
+
inPos += len;
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
for (const d of decos) {
|
|
804
|
+
const s = Math.max(d.ds, inPos);
|
|
805
|
+
const e = Math.min(d.de, inPos + len - 1);
|
|
806
|
+
if (s <= e) d.pieces.push([outPos + s - inPos, outPos + e - inPos]);
|
|
807
|
+
}
|
|
808
|
+
let r = a;
|
|
809
|
+
if (a.body != null && b.body != null) {
|
|
810
|
+
const { coverType, body } = transformBody(b, a, !xLeft);
|
|
811
|
+
r = { ...a, t: "r", coverType, body };
|
|
812
|
+
}
|
|
813
|
+
if (r.coverType === 1 && r.body) {
|
|
814
|
+
const keep = [];
|
|
815
|
+
for (const d of decos) {
|
|
816
|
+
if (!(d.de >= inPos && d.ds < inPos + len)) continue;
|
|
817
|
+
const ahead = d.de >= inPos + len ? [[outPos + len + Math.max(d.ds, inPos + len) - (inPos + len), outPos + d.de - inPos]] : [];
|
|
818
|
+
for (const [s, e] of [...d.pieces, ...ahead]) keep.push({ id: d.id, type: d.type, startIndex: s - outPos, endIndex: e - outPos });
|
|
819
|
+
}
|
|
820
|
+
if (keep.length) r = { ...r, body: { ...r.body, customDecorations: [...r.body.customDecorations ?? [], ...keep] } };
|
|
821
|
+
}
|
|
822
|
+
push(out, r);
|
|
823
|
+
inPos += len;
|
|
824
|
+
outPos += len;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
return trimEnd(out);
|
|
828
|
+
}
|
|
829
|
+
var sink = null;
|
|
830
|
+
var ours = {
|
|
831
|
+
name: "text-x",
|
|
832
|
+
uri: TextX.uri,
|
|
833
|
+
apply: (doc, actions) => TextX.apply(doc, actions),
|
|
834
|
+
compose: (a, b) => TextX.compose(a, b),
|
|
835
|
+
isNoop: (a) => TextX.isNoop(a),
|
|
836
|
+
invert: (a) => TextX.invert(a),
|
|
837
|
+
makeInvertible: (a, doc) => TextX.makeInvertible(a, doc),
|
|
838
|
+
transform: (x, y, side) => transformTextX(x, y, side, sink ?? [])
|
|
839
|
+
};
|
|
840
|
+
function transformDocsOp(x, y, side) {
|
|
841
|
+
if (x == null) return { op: null, lost: [] };
|
|
842
|
+
if (y == null) return { op: x, lost: [] };
|
|
843
|
+
const lost = [];
|
|
844
|
+
sink = lost;
|
|
845
|
+
json1.type.registerSubtype(ours);
|
|
846
|
+
try {
|
|
847
|
+
const op = json1.type.transformNoConflict(normalizeDocsOp(x), normalizeDocsOp(y), side);
|
|
848
|
+
return { op: dropEmptyStyleRetains(op), lost };
|
|
849
|
+
} finally {
|
|
850
|
+
json1.type.registerSubtype(TextX);
|
|
851
|
+
sink = null;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
function composeDocsOps(a, b) {
|
|
855
|
+
if (a == null) return b;
|
|
856
|
+
if (b == null) return a;
|
|
857
|
+
json1.type.registerSubtype(TextX);
|
|
858
|
+
return json1.type.compose(a, b);
|
|
859
|
+
}
|
|
860
|
+
function isNoopDocsOp(op) {
|
|
861
|
+
return op == null || Array.isArray(op) && op.length === 0 || json1.type.isNoop(op);
|
|
862
|
+
}
|
|
863
|
+
function applyDocsOp(doc, op) {
|
|
864
|
+
if (isNoopDocsOp(op)) return doc;
|
|
865
|
+
return JSONX.apply(structuredClone(doc), op);
|
|
866
|
+
}
|
|
867
|
+
function invertDocsOp(op, preDoc) {
|
|
868
|
+
if (isNoopDocsOp(op)) return null;
|
|
869
|
+
return JSONX.invertWithDoc(structuredClone(op), preDoc);
|
|
870
|
+
}
|
|
871
|
+
var DOCS_TRANSFORM_VERSION = "univer-1.0.4+docs-ot-r6";
|
|
872
|
+
|
|
873
|
+
// src/univer/collab/docs-ot/state-hash.ts
|
|
874
|
+
var DOC_LOCAL_KEYS = ["rev", "__matrxCollabCovered", "__collab", "disabled", "layoutState", "textRanges", "selections"];
|
|
875
|
+
function sortDeep(v) {
|
|
876
|
+
if (Array.isArray(v)) return v.map(sortDeep);
|
|
877
|
+
if (v && typeof v === "object") {
|
|
878
|
+
const out = {};
|
|
879
|
+
for (const k of Object.keys(v).sort()) {
|
|
880
|
+
const x = v[k];
|
|
881
|
+
if (x === void 0 || x === null) continue;
|
|
882
|
+
if (Array.isArray(x) && x.length === 0) continue;
|
|
883
|
+
if (x && typeof x === "object" && !Array.isArray(x) && Object.keys(x).length === 0) continue;
|
|
884
|
+
out[k] = sortDeep(x);
|
|
885
|
+
}
|
|
886
|
+
return out;
|
|
887
|
+
}
|
|
888
|
+
return v;
|
|
889
|
+
}
|
|
890
|
+
var sameTs = (a, b) => JSON.stringify(sortDeep(a ?? {})) === JSON.stringify(sortDeep(b ?? {}));
|
|
891
|
+
function canonicalRuns(runs) {
|
|
892
|
+
const out = [];
|
|
893
|
+
for (const r of [...runs ?? []].sort((x, y) => x.st - y.st)) {
|
|
894
|
+
if (r.ed <= r.st) continue;
|
|
895
|
+
const ts = sortDeep(r.ts ?? {});
|
|
896
|
+
if (Object.keys(ts).length === 0) continue;
|
|
897
|
+
const last = out[out.length - 1];
|
|
898
|
+
if (last && last.ed === r.st && sameTs(last.ts, ts)) last.ed = r.ed;
|
|
899
|
+
else out.push({ st: r.st, ed: r.ed, ts });
|
|
900
|
+
}
|
|
901
|
+
return out;
|
|
902
|
+
}
|
|
903
|
+
function foldPieces(items, keyOf) {
|
|
904
|
+
const groups = /* @__PURE__ */ new Map();
|
|
905
|
+
for (const it of items ?? []) {
|
|
906
|
+
const k = keyOf(it);
|
|
907
|
+
const g = groups.get(k) ?? [];
|
|
908
|
+
g.push(it);
|
|
909
|
+
groups.set(k, g);
|
|
910
|
+
}
|
|
911
|
+
const out = [];
|
|
912
|
+
for (const list of groups.values()) {
|
|
913
|
+
list.sort((a, b) => a.startIndex - b.startIndex);
|
|
914
|
+
let cur = null;
|
|
915
|
+
for (const it of list) {
|
|
916
|
+
if (cur && cur.endIndex + 1 >= it.startIndex) cur.endIndex = Math.max(cur.endIndex, it.endIndex);
|
|
917
|
+
else {
|
|
918
|
+
if (cur) out.push(cur);
|
|
919
|
+
cur = { ...it };
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
if (cur) out.push(cur);
|
|
923
|
+
}
|
|
924
|
+
return out;
|
|
925
|
+
}
|
|
926
|
+
var rootId = (id) => String(id).split("$")[0];
|
|
927
|
+
function canonicalBody(body) {
|
|
928
|
+
if (!body) return body;
|
|
929
|
+
const b = { ...body };
|
|
930
|
+
b.textRuns = canonicalRuns(b.textRuns);
|
|
931
|
+
if (Array.isArray(b.paragraphs)) b.paragraphs = b.paragraphs.map(({ paragraphId: _p, ...p }) => p);
|
|
932
|
+
b.customRanges = foldPieces(b.customRanges, (r) => JSON.stringify([rootId(r.rangeId), r.rangeType, sortDeep(r.properties ?? {})])).map((r) => ({
|
|
933
|
+
...r,
|
|
934
|
+
rangeId: rootId(r.rangeId)
|
|
935
|
+
}));
|
|
936
|
+
b.customDecorations = foldPieces(b.customDecorations, (d) => JSON.stringify([d.id, d.type]));
|
|
937
|
+
for (const k of ["paragraphs", "customRanges", "customBlocks", "tables", "sectionBreaks", "customDecorations"]) {
|
|
938
|
+
if (Array.isArray(b[k])) {
|
|
939
|
+
b[k] = [...b[k]].sort(
|
|
940
|
+
(x, y) => (x.startIndex ?? 0) - (y.startIndex ?? 0) || String(x.rangeId ?? x.blockId ?? x.tableId ?? "").localeCompare(String(y.rangeId ?? y.blockId ?? y.tableId ?? ""))
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
return b;
|
|
945
|
+
}
|
|
946
|
+
function canonicalDocsSnapshot(snapshot) {
|
|
947
|
+
const s = structuredClone(snapshot ?? {});
|
|
948
|
+
delete s.id;
|
|
949
|
+
for (const k of DOC_LOCAL_KEYS) delete s[k];
|
|
950
|
+
s.body = canonicalBody(s.body);
|
|
951
|
+
for (const seg of ["headers", "footers", "notes"]) {
|
|
952
|
+
if (!s[seg]) continue;
|
|
953
|
+
for (const id of Object.keys(s[seg])) s[seg][id] = { ...s[seg][id], body: canonicalBody(s[seg][id].body) };
|
|
954
|
+
}
|
|
955
|
+
return sortDeep(s);
|
|
956
|
+
}
|
|
957
|
+
var canonicalDocsString = (snapshot) => JSON.stringify(canonicalDocsSnapshot(snapshot));
|
|
958
|
+
async function docsStateHash(snapshot) {
|
|
959
|
+
const bytes = new TextEncoder().encode(canonicalDocsString(snapshot));
|
|
960
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
961
|
+
return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
962
|
+
}
|
|
963
|
+
function stampDeterministicIds(snapshot) {
|
|
964
|
+
const s = structuredClone(snapshot);
|
|
965
|
+
const stamp = (body, scope) => {
|
|
966
|
+
if (!body) return;
|
|
967
|
+
for (const p of body.paragraphs ?? []) if (!p.paragraphId) p.paragraphId = `p_${scope}${p.startIndex}`;
|
|
968
|
+
for (const sb of body.sectionBreaks ?? []) if (!sb.sectionId) sb.sectionId = `s_${scope}${sb.startIndex}`;
|
|
969
|
+
};
|
|
970
|
+
stamp(s?.body, "");
|
|
971
|
+
for (const seg of ["headers", "footers", "notes"]) for (const id of Object.keys(s?.[seg] ?? {})) stamp(s[seg][id]?.body, `${id}_`);
|
|
972
|
+
return s;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// src/univer/collab/docs-ot/room.ts
|
|
976
|
+
import { isOpBarrier, openOpSession } from "@ai-matrx/data/realtime/collab";
|
|
977
|
+
var RICH_TEXT_EDITING = "doc.mutation.rich-text-editing";
|
|
978
|
+
var DOCS_COLLAB_PROTO = 1;
|
|
979
|
+
function branches(op) {
|
|
980
|
+
if (!Array.isArray(op) || op.length === 0) return [];
|
|
981
|
+
return Array.isArray(op[0]) ? op : [op];
|
|
982
|
+
}
|
|
983
|
+
var touchesPageSetup = (op) => branches(op).some((b) => b[0] === "documentStyle");
|
|
984
|
+
function segmentOf(branch) {
|
|
985
|
+
return (branch[0] === "headers" || branch[0] === "footers") && typeof branch[1] === "string" ? branch[1] : "";
|
|
986
|
+
}
|
|
987
|
+
function undoStacksOf(service) {
|
|
988
|
+
const s = service;
|
|
989
|
+
if (!s || !(s._undoStacks instanceof Map) || !(s._redoStacks instanceof Map)) return null;
|
|
990
|
+
return { undo: s._undoStacks, redo: s._redoStacks };
|
|
991
|
+
}
|
|
992
|
+
function rebaseUndoStack(items, remote) {
|
|
993
|
+
let dropped = 0;
|
|
994
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
995
|
+
const item = items[i];
|
|
996
|
+
let ok = true;
|
|
997
|
+
for (const list of [item.undoMutations, item.redoMutations]) {
|
|
998
|
+
for (const m of list ?? []) {
|
|
999
|
+
if (m.id !== RICH_TEXT_EDITING) continue;
|
|
1000
|
+
try {
|
|
1001
|
+
if (touchesPageSetup(m.params.actions) && touchesPageSetup(remote)) throw new Error("page setup");
|
|
1002
|
+
const r = transformDocsOp(m.params.actions, remote, "left");
|
|
1003
|
+
if (isNoopDocsOp(r.op)) ok = false;
|
|
1004
|
+
else {
|
|
1005
|
+
m.params = { ...m.params, actions: r.op };
|
|
1006
|
+
delete m.params.textRanges;
|
|
1007
|
+
delete m.params.layoutState;
|
|
1008
|
+
}
|
|
1009
|
+
} catch {
|
|
1010
|
+
ok = false;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (!ok) {
|
|
1015
|
+
items.splice(i, 1);
|
|
1016
|
+
dropped++;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
return dropped;
|
|
1020
|
+
}
|
|
1021
|
+
function openDocsOtRoom(o) {
|
|
1022
|
+
const { port } = o;
|
|
1023
|
+
let session = null;
|
|
1024
|
+
let composing = false;
|
|
1025
|
+
let queue = [];
|
|
1026
|
+
const drainWaiters = [];
|
|
1027
|
+
const notice = (n) => {
|
|
1028
|
+
try {
|
|
1029
|
+
o.onNotice?.(n);
|
|
1030
|
+
} catch (err) {
|
|
1031
|
+
console.warn("[docs-ot] notice handler failed", err);
|
|
1032
|
+
}
|
|
1033
|
+
};
|
|
1034
|
+
const applyNow = (op) => {
|
|
1035
|
+
for (const b of branches(op)) {
|
|
1036
|
+
const params = { segmentId: segmentOf(b), actions: b };
|
|
1037
|
+
port.applyRemote(RICH_TEXT_EDITING, params);
|
|
1038
|
+
}
|
|
1039
|
+
for (const t of port.undoTargets()) {
|
|
1040
|
+
const stacks = undoStacksOf(t.service);
|
|
1041
|
+
if (!stacks) continue;
|
|
1042
|
+
for (const m of [stacks.undo, stacks.redo]) {
|
|
1043
|
+
const items = m.get(t.unitId);
|
|
1044
|
+
if (items?.length) rebaseUndoStack(items, op);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
port.markDirty();
|
|
1048
|
+
};
|
|
1049
|
+
const drain = () => {
|
|
1050
|
+
const held = queue;
|
|
1051
|
+
queue = [];
|
|
1052
|
+
try {
|
|
1053
|
+
for (const op of held) applyNow(op);
|
|
1054
|
+
} catch (err) {
|
|
1055
|
+
console.warn("[docs-ot] a held remote edit failed after composition; reloading this seat", err);
|
|
1056
|
+
session?.rebuild("ime_drain_failed");
|
|
1057
|
+
}
|
|
1058
|
+
for (const w of drainWaiters.splice(0)) w();
|
|
1059
|
+
};
|
|
1060
|
+
const adapter = {
|
|
1061
|
+
version: DOCS_TRANSFORM_VERSION,
|
|
1062
|
+
transform(x, y, side) {
|
|
1063
|
+
if (x != null && y != null && (touchesPageSetup(x) || touchesPageSetup(y))) return { barrier: true, reason: "page_setup" };
|
|
1064
|
+
const r = transformDocsOp(x, y, side);
|
|
1065
|
+
return { op: isNoopDocsOp(r.op) ? null : r.op, lost: r.lost };
|
|
1066
|
+
},
|
|
1067
|
+
compose: (a, b) => composeDocsOps(a, b),
|
|
1068
|
+
isNoop: (op) => isNoopDocsOp(op),
|
|
1069
|
+
encode: (op) => [{ cmd: "docs.edit", mutations: [{ id: RICH_TEXT_EDITING, params: { segmentId: "", actions: op } }] }],
|
|
1070
|
+
decode(groups) {
|
|
1071
|
+
let op = null;
|
|
1072
|
+
for (const g of groups) {
|
|
1073
|
+
for (const m of g.mutations) {
|
|
1074
|
+
if (m.id !== RICH_TEXT_EDITING) throw new Error(`a Docs room cannot apply ${m.id}`);
|
|
1075
|
+
op = composeDocsOps(op, m.params.actions ?? null);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return op;
|
|
1079
|
+
},
|
|
1080
|
+
apply(op) {
|
|
1081
|
+
if (isNoopDocsOp(op)) return true;
|
|
1082
|
+
if (composing) {
|
|
1083
|
+
queue.push(op);
|
|
1084
|
+
return true;
|
|
1085
|
+
}
|
|
1086
|
+
applyNow(op);
|
|
1087
|
+
return true;
|
|
1088
|
+
},
|
|
1089
|
+
async load(snapshot) {
|
|
1090
|
+
const next = stampDeterministicIds(snapshot ?? {});
|
|
1091
|
+
queue = [];
|
|
1092
|
+
const current = port.snapshot();
|
|
1093
|
+
if (current && canonicalDocsString(current) === canonicalDocsString(next)) return;
|
|
1094
|
+
port.load(next);
|
|
1095
|
+
for (const t of port.undoTargets()) {
|
|
1096
|
+
const stacks = undoStacksOf(t.service);
|
|
1097
|
+
stacks?.undo.delete(t.unitId);
|
|
1098
|
+
stacks?.redo.delete(t.unitId);
|
|
1099
|
+
}
|
|
1100
|
+
},
|
|
1101
|
+
snapshot: () => port.snapshot(),
|
|
1102
|
+
async hash() {
|
|
1103
|
+
if (composing || queue.length) {
|
|
1104
|
+
await new Promise((resolve) => {
|
|
1105
|
+
drainWaiters.push(resolve);
|
|
1106
|
+
setTimeout(resolve, 5e3);
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
return docsStateHash(port.snapshot());
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
const onLocal = (m) => {
|
|
1113
|
+
if (!session) return;
|
|
1114
|
+
if (m.id !== RICH_TEXT_EDITING) {
|
|
1115
|
+
if (!m.content) return;
|
|
1116
|
+
notice({ kind: "not_shared", mutationId: m.id });
|
|
1117
|
+
session.rebuild(`not_shared:${m.id}`);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
const p = m.params ?? {};
|
|
1121
|
+
const applied = p.actions ?? null;
|
|
1122
|
+
if (isNoopDocsOp(applied)) return;
|
|
1123
|
+
let op = normalizeDocsOp(structuredClone(applied));
|
|
1124
|
+
let inverse;
|
|
1125
|
+
try {
|
|
1126
|
+
inverse = m.pre != null ? invertDocsOp(applied, m.pre) : void 0;
|
|
1127
|
+
} catch {
|
|
1128
|
+
inverse = void 0;
|
|
1129
|
+
}
|
|
1130
|
+
if (queue.length) {
|
|
1131
|
+
inverse = void 0;
|
|
1132
|
+
const next = [];
|
|
1133
|
+
for (const q of queue) {
|
|
1134
|
+
const l2 = adapter.transform(op, q, "left");
|
|
1135
|
+
const q2 = adapter.transform(q, op, "right");
|
|
1136
|
+
if (isOpBarrier(l2) || isOpBarrier(q2)) {
|
|
1137
|
+
queue = [];
|
|
1138
|
+
session.rebuild("ime_barrier");
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
op = l2.op;
|
|
1142
|
+
next.push(q2.op);
|
|
1143
|
+
}
|
|
1144
|
+
queue = next.filter((q) => !isNoopDocsOp(q));
|
|
1145
|
+
if (isNoopDocsOp(op)) return;
|
|
1146
|
+
}
|
|
1147
|
+
if (!session.local(op, { ...inverse !== void 0 ? { inverse } : {}, label: m.cmd ?? RICH_TEXT_EDITING })) notice({ kind: "not_live" });
|
|
1148
|
+
};
|
|
1149
|
+
const unsubLocal = port.onLocal(onLocal);
|
|
1150
|
+
const target = o.compositionTarget === void 0 ? typeof document !== "undefined" ? document : null : o.compositionTarget;
|
|
1151
|
+
const onStart = () => {
|
|
1152
|
+
composing = true;
|
|
1153
|
+
};
|
|
1154
|
+
const onEnd = () => {
|
|
1155
|
+
composing = false;
|
|
1156
|
+
setTimeout(() => {
|
|
1157
|
+
if (!composing) drain();
|
|
1158
|
+
}, 0);
|
|
1159
|
+
};
|
|
1160
|
+
target?.addEventListener("compositionstart", onStart, true);
|
|
1161
|
+
target?.addEventListener("compositionend", onEnd, true);
|
|
1162
|
+
session = openOpSession({
|
|
1163
|
+
transport: o.transport,
|
|
1164
|
+
adapter,
|
|
1165
|
+
proto: DOCS_COLLAB_PROTO,
|
|
1166
|
+
clientId: o.clientId,
|
|
1167
|
+
...o.presence ? { presence: o.presence } : {},
|
|
1168
|
+
onNotice: (n) => notice(n),
|
|
1169
|
+
...o.onIncident ? { onIncident: o.onIncident } : {},
|
|
1170
|
+
...o.onStatus ? { onStatus: o.onStatus } : {}
|
|
1171
|
+
});
|
|
1172
|
+
const isSaver = () => {
|
|
1173
|
+
if (!o.presence) return true;
|
|
1174
|
+
return o.presence.peers().every((p) => p.clientId === o.clientId || p.clientId > o.clientId);
|
|
1175
|
+
};
|
|
1176
|
+
return {
|
|
1177
|
+
session,
|
|
1178
|
+
isSaver,
|
|
1179
|
+
async write(reason) {
|
|
1180
|
+
if (!session) return false;
|
|
1181
|
+
if (reason !== "manual" && !isSaver()) return false;
|
|
1182
|
+
const out = await session.save();
|
|
1183
|
+
if (out.saved) return true;
|
|
1184
|
+
if (out.reason === "not_quiet" || out.reason === "changed") setTimeout(() => port.markDirty(), 1500);
|
|
1185
|
+
return false;
|
|
1186
|
+
},
|
|
1187
|
+
async replace(snapshot) {
|
|
1188
|
+
port.load(stampDeterministicIds(snapshot));
|
|
1189
|
+
const loaded = port.snapshot();
|
|
1190
|
+
if (!loaded) throw new Error("The document is not open in this tab.");
|
|
1191
|
+
const stateHash = await docsStateHash(loaded);
|
|
1192
|
+
await o.replace({ snapshot: loaded, stateHash, clientId: o.clientId });
|
|
1193
|
+
},
|
|
1194
|
+
stop() {
|
|
1195
|
+
unsubLocal();
|
|
1196
|
+
target?.removeEventListener("compositionstart", onStart, true);
|
|
1197
|
+
target?.removeEventListener("compositionend", onEnd, true);
|
|
1198
|
+
session?.close();
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// src/docs/document-ot-room.ts
|
|
1204
|
+
import * as Y2 from "yjs";
|
|
1205
|
+
import { Awareness as Awareness2 } from "y-protocols/awareness";
|
|
1206
|
+
import {
|
|
1207
|
+
awarenessOpPresence,
|
|
1208
|
+
collabReplace,
|
|
1209
|
+
collabRoomExists,
|
|
1210
|
+
SupabaseYjsProvider,
|
|
1211
|
+
supabaseOpTransport
|
|
1212
|
+
} from "@ai-matrx/data/realtime/collab";
|
|
1213
|
+
|
|
1214
|
+
// src/docs/document-service.ts
|
|
1215
|
+
import { requireOrganizationContext } from "@ai-matrx/api";
|
|
1216
|
+
import { getClaimsUser } from "@ai-matrx/auth";
|
|
1217
|
+
import { readAllRows, tryWriteOne, writeOneRow } from "@ai-matrx/data/db";
|
|
1218
|
+
|
|
1219
|
+
// src/docs/ports.ts
|
|
1220
|
+
var SLOT = /* @__PURE__ */ Symbol.for("ai-matrx.office.docs-host");
|
|
1221
|
+
var slot = () => {
|
|
1222
|
+
const g = globalThis;
|
|
1223
|
+
return g[SLOT] ??= {};
|
|
1224
|
+
};
|
|
1225
|
+
function getDocsHost() {
|
|
1226
|
+
const ports = slot().ports;
|
|
1227
|
+
if (!ports) throw new Error("@ai-matrx/office/docs: configureDocsHost(ports) was not called before the document service ran.");
|
|
1228
|
+
return ports;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
// src/docs/document-service.ts
|
|
1232
|
+
var db = () => getDocsHost().getSupabase();
|
|
1233
|
+
async function getLatestDocumentSnapshot(documentId) {
|
|
1234
|
+
const { data, error } = await db().schema("workbench").from("udt_document_snapshots").select("*").eq("document_id", documentId).order("created_at", { ascending: false }).limit(1).maybeSingle();
|
|
1235
|
+
if (error) return { success: false, error: error.message };
|
|
1236
|
+
return { success: true, data: data ?? null };
|
|
1237
|
+
}
|
|
1238
|
+
async function saveDocumentSnapshot(args) {
|
|
1239
|
+
const { data: userData } = await getClaimsUser(db());
|
|
1240
|
+
const { data, error } = await db().schema("workbench").from("udt_document_snapshots").insert({
|
|
1241
|
+
document_id: args.documentId,
|
|
1242
|
+
snapshot: args.snapshot,
|
|
1243
|
+
label: args.label ?? null,
|
|
1244
|
+
origin: args.origin ?? "autosave",
|
|
1245
|
+
created_by: userData?.user?.id ?? null
|
|
1246
|
+
}).select("*").single();
|
|
1247
|
+
if (error) return { success: false, error: error.message };
|
|
1248
|
+
await db().schema("workbench").from("udt_documents").update({ updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", args.documentId);
|
|
1249
|
+
return { success: true, data };
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// src/docs/document-ot-room.ts
|
|
1253
|
+
var db2 = () => getDocsHost().getSupabase();
|
|
1254
|
+
async function loadSnapshotRow(documentId, ref) {
|
|
1255
|
+
if (ref === "none") {
|
|
1256
|
+
const res = await getLatestDocumentSnapshot(documentId);
|
|
1257
|
+
if (!res.success) throw new Error(res.error);
|
|
1258
|
+
return res.data?.snapshot ?? null;
|
|
1259
|
+
}
|
|
1260
|
+
const { data, error } = await getDocsHost().getSupabase().schema("workbench").from("udt_document_snapshots").select("snapshot").eq("id", ref).maybeSingle();
|
|
1261
|
+
if (error) throw new Error(error.message);
|
|
1262
|
+
if (!data) throw new Error(`The saved version ${ref} of this document is missing.`);
|
|
1263
|
+
return data.snapshot ?? null;
|
|
1264
|
+
}
|
|
1265
|
+
async function documentUsesOrderedRoom(documentId, knob, isNewDocument) {
|
|
1266
|
+
try {
|
|
1267
|
+
if (await collabRoomExists(db2(), documentId)) return true;
|
|
1268
|
+
} catch (err) {
|
|
1269
|
+
console.warn("[document] could not ask whether this document has a co-editing room; opening the relay room", err);
|
|
1270
|
+
return false;
|
|
1271
|
+
}
|
|
1272
|
+
if (knob === "all") return true;
|
|
1273
|
+
if (knob === "new") return isNewDocument();
|
|
1274
|
+
return false;
|
|
1275
|
+
}
|
|
1276
|
+
function pickColor2(uid) {
|
|
1277
|
+
const palette = ["#ef4444", "#f59e0b", "#10b981", "#3b82f6", "#8b5cf6", "#ec4899", "#14b8a6", "#f97316"];
|
|
1278
|
+
let h = 2166136261;
|
|
1279
|
+
for (let i = 0; i < uid.length; i++) {
|
|
1280
|
+
h ^= uid.charCodeAt(i);
|
|
1281
|
+
h = Math.imul(h, 16777619);
|
|
1282
|
+
}
|
|
1283
|
+
return palette[Math.abs(h) % palette.length] ?? "#ec4899";
|
|
1284
|
+
}
|
|
1285
|
+
function documentOtFactory(o) {
|
|
1286
|
+
return async (port, onAwareness) => {
|
|
1287
|
+
const latest = await getLatestDocumentSnapshot(o.documentId);
|
|
1288
|
+
if (!latest.success) throw new Error(latest.error);
|
|
1289
|
+
if (!latest.data) {
|
|
1290
|
+
const mounted = port.snapshot();
|
|
1291
|
+
if (mounted) {
|
|
1292
|
+
const saved = await saveDocumentSnapshot({ documentId: o.documentId, snapshot: mounted, origin: "autosave" });
|
|
1293
|
+
if (!saved.success) throw new Error(saved.error);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
const doc = new Y2.Doc();
|
|
1297
|
+
const awareness = new Awareness2(doc);
|
|
1298
|
+
awareness.setLocalState({ uid: o.uid, clientId: o.clientId, name: o.displayName ?? o.uid.slice(0, 8), color: pickColor2(o.uid), ts: Date.now() });
|
|
1299
|
+
const publishAwareness = () => onAwareness({ states: new Map(awareness.getStates()), selfUid: o.uid });
|
|
1300
|
+
awareness.on("change", publishAwareness);
|
|
1301
|
+
const provider = new SupabaseYjsProvider({
|
|
1302
|
+
workbookId: o.documentId,
|
|
1303
|
+
channelPrefix: "document",
|
|
1304
|
+
clientId: o.clientId,
|
|
1305
|
+
doc,
|
|
1306
|
+
awareness,
|
|
1307
|
+
...o.readAntiEntropyIntervalMs ? { readAntiEntropyIntervalMs: o.readAntiEntropyIntervalMs } : {}
|
|
1308
|
+
});
|
|
1309
|
+
await provider.connect();
|
|
1310
|
+
publishAwareness();
|
|
1311
|
+
const client = db2();
|
|
1312
|
+
const room = openDocsOtRoom({
|
|
1313
|
+
port,
|
|
1314
|
+
clientId: o.clientId,
|
|
1315
|
+
presence: awarenessOpPresence(awareness),
|
|
1316
|
+
transport: supabaseOpTransport({
|
|
1317
|
+
client,
|
|
1318
|
+
roomId: o.documentId,
|
|
1319
|
+
kind: "univer_doc",
|
|
1320
|
+
store: {
|
|
1321
|
+
loadSnapshot: (ref) => loadSnapshotRow(o.documentId, ref),
|
|
1322
|
+
async writeSnapshot(snapshot) {
|
|
1323
|
+
const res = await saveDocumentSnapshot({ documentId: o.documentId, snapshot, origin: "autosave" });
|
|
1324
|
+
if (!res.success) throw new Error(res.error);
|
|
1325
|
+
return res.data.id;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}),
|
|
1329
|
+
replace: ({ snapshot, stateHash, clientId }) => collabReplace(client, { roomId: o.documentId, snapshot, stateHash, origin: "restore", clientId }),
|
|
1330
|
+
...o.onNotice ? { onNotice: o.onNotice } : {},
|
|
1331
|
+
...o.onIncident ? { onIncident: o.onIncident } : {},
|
|
1332
|
+
...o.onStatus ? { onStatus: o.onStatus } : {}
|
|
1333
|
+
});
|
|
1334
|
+
await room.session.ready;
|
|
1335
|
+
return {
|
|
1336
|
+
isSaver: () => room.isSaver(),
|
|
1337
|
+
write: (reason) => room.write(reason),
|
|
1338
|
+
replace: (snapshot) => room.replace(snapshot),
|
|
1339
|
+
stop: () => {
|
|
1340
|
+
room.stop();
|
|
1341
|
+
awareness.off("change", publishAwareness);
|
|
1342
|
+
provider.disconnect();
|
|
1343
|
+
awareness.destroy();
|
|
1344
|
+
doc.destroy();
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
361
1349
|
export {
|
|
362
1350
|
COLLAB_COVERED_KEY,
|
|
1351
|
+
DOCS_COLLAB_PROTO,
|
|
1352
|
+
DOCS_TRANSFORM_VERSION,
|
|
1353
|
+
DOC_LOCAL_KEYS,
|
|
1354
|
+
RICH_TEXT_EDITING,
|
|
363
1355
|
SENDER_VIEW_PARAMS,
|
|
364
1356
|
WorkbookCollabSession,
|
|
1357
|
+
applyDocsOp,
|
|
1358
|
+
canonicalDocsSnapshot,
|
|
1359
|
+
canonicalDocsString,
|
|
1360
|
+
composeDocsOps,
|
|
1361
|
+
docsStateHash,
|
|
1362
|
+
documentOtFactory,
|
|
1363
|
+
documentUsesOrderedRoom,
|
|
1364
|
+
dropEmptyStyleRetains,
|
|
1365
|
+
invertDocsOp,
|
|
365
1366
|
isCellEditorScratch,
|
|
1367
|
+
isNoopDocsOp,
|
|
1368
|
+
normalizeDocsOp,
|
|
1369
|
+
openDocsOtRoom,
|
|
1370
|
+
rebaseUndoStack,
|
|
366
1371
|
stampCovered,
|
|
1372
|
+
stampDeterministicIds,
|
|
367
1373
|
stripSenderView,
|
|
368
|
-
takeCovered
|
|
1374
|
+
takeCovered,
|
|
1375
|
+
transformDocsOp,
|
|
1376
|
+
transformTextX,
|
|
1377
|
+
undoStacksOf
|
|
369
1378
|
};
|
|
370
1379
|
//# sourceMappingURL=univer-collab.js.map
|