ooxml-core 1.22.0 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +7 -2
  2. package/dist/automation/index.cjs +6 -4
  3. package/dist/automation/index.mjs +6 -4
  4. package/dist/collab/embedding.d.ts +7 -0
  5. package/dist/collab/embedding.d.ts.map +1 -1
  6. package/dist/collab/index.cjs +189 -0
  7. package/dist/collab/index.d.ts +2 -0
  8. package/dist/collab/index.d.ts.map +1 -1
  9. package/dist/collab/index.mjs +186 -1
  10. package/dist/collab/write-back-lifecycle.d.ts +33 -0
  11. package/dist/collab/write-back-lifecycle.d.ts.map +1 -0
  12. package/dist/collab/write-back.d.ts +75 -0
  13. package/dist/collab/write-back.d.ts.map +1 -0
  14. package/dist/visio/collab/baseline.d.ts +36 -0
  15. package/dist/visio/collab/baseline.d.ts.map +1 -0
  16. package/dist/visio/collab/bind.d.ts +20 -2
  17. package/dist/visio/collab/bind.d.ts.map +1 -1
  18. package/dist/visio/collab/compose-lists.d.ts +27 -0
  19. package/dist/visio/collab/compose-lists.d.ts.map +1 -0
  20. package/dist/visio/collab/compose.d.ts +6 -2
  21. package/dist/visio/collab/compose.d.ts.map +1 -1
  22. package/dist/visio/collab/fixups.d.ts +5 -0
  23. package/dist/visio/collab/fixups.d.ts.map +1 -0
  24. package/dist/visio/collab/index.cjs +1499 -362
  25. package/dist/visio/collab/index.d.ts +3 -2
  26. package/dist/visio/collab/index.d.ts.map +1 -1
  27. package/dist/visio/collab/index.mjs +1492 -363
  28. package/dist/visio/collab/keys.d.ts +30 -1
  29. package/dist/visio/collab/keys.d.ts.map +1 -1
  30. package/dist/visio/collab/package.d.ts +22 -1
  31. package/dist/visio/collab/package.d.ts.map +1 -1
  32. package/dist/visio/collab/pieces.d.ts +34 -0
  33. package/dist/visio/collab/pieces.d.ts.map +1 -0
  34. package/dist/visio/collab/read-ids.d.ts +29 -0
  35. package/dist/visio/collab/read-ids.d.ts.map +1 -0
  36. package/dist/visio/collab/read-parts.d.ts +34 -0
  37. package/dist/visio/collab/read-parts.d.ts.map +1 -0
  38. package/dist/visio/collab/read-shapes.d.ts +35 -0
  39. package/dist/visio/collab/read-shapes.d.ts.map +1 -0
  40. package/dist/visio/collab/read-util.d.ts +20 -0
  41. package/dist/visio/collab/read-util.d.ts.map +1 -0
  42. package/dist/visio/collab/read.d.ts +40 -19
  43. package/dist/visio/collab/read.d.ts.map +1 -1
  44. package/dist/visio/collab/schema.d.ts +55 -1
  45. package/dist/visio/collab/schema.d.ts.map +1 -1
  46. package/dist/visio/collab/snapshot-contents.d.ts +9 -0
  47. package/dist/visio/collab/snapshot-contents.d.ts.map +1 -0
  48. package/dist/visio/collab/snapshot-io.d.ts +26 -0
  49. package/dist/visio/collab/snapshot-io.d.ts.map +1 -0
  50. package/dist/visio/collab/snapshot-parts.d.ts +14 -0
  51. package/dist/visio/collab/snapshot-parts.d.ts.map +1 -0
  52. package/dist/visio/collab/snapshot.d.ts +31 -24
  53. package/dist/visio/collab/snapshot.d.ts.map +1 -1
  54. package/dist/visio/collab/write-parts.d.ts +8 -0
  55. package/dist/visio/collab/write-parts.d.ts.map +1 -0
  56. package/dist/visio/collab/write-shapes.d.ts +30 -0
  57. package/dist/visio/collab/write-shapes.d.ts.map +1 -0
  58. package/dist/visio/collab/write.d.ts +4 -16
  59. package/dist/visio/collab/write.d.ts.map +1 -1
  60. package/dist/visio/index.cjs +57 -4
  61. package/dist/visio/index.d.ts +1 -0
  62. package/dist/visio/index.d.ts.map +1 -1
  63. package/dist/visio/index.mjs +56 -5
  64. package/dist/visio/parse-incremental.d.ts +16 -0
  65. package/dist/visio/parse-incremental.d.ts.map +1 -0
  66. package/dist/visio/parser.d.ts +7 -0
  67. package/dist/visio/parser.d.ts.map +1 -1
  68. package/dist/visio/ui/collab-options.d.ts +10 -5
  69. package/dist/visio/ui/collab-options.d.ts.map +1 -1
  70. package/dist/visio/ui/index.cjs +6 -4
  71. package/dist/visio/ui/index.mjs +6 -4
  72. package/package.json +1 -1
@@ -28,6 +28,143 @@ var Y__namespace = /*#__PURE__*/_interopNamespace(Y);
28
28
  var JSZip__default = /*#__PURE__*/_interopDefault(JSZip);
29
29
 
30
30
  // visio/collab/bind.ts
31
+
32
+ // digest/sha256.ts
33
+ var K = new Uint32Array([
34
+ 1116352408,
35
+ 1899447441,
36
+ 3049323471,
37
+ 3921009573,
38
+ 961987163,
39
+ 1508970993,
40
+ 2453635748,
41
+ 2870763221,
42
+ 3624381080,
43
+ 310598401,
44
+ 607225278,
45
+ 1426881987,
46
+ 1925078388,
47
+ 2162078206,
48
+ 2614888103,
49
+ 3248222580,
50
+ 3835390401,
51
+ 4022224774,
52
+ 264347078,
53
+ 604807628,
54
+ 770255983,
55
+ 1249150122,
56
+ 1555081692,
57
+ 1996064986,
58
+ 2554220882,
59
+ 2821834349,
60
+ 2952996808,
61
+ 3210313671,
62
+ 3336571891,
63
+ 3584528711,
64
+ 113926993,
65
+ 338241895,
66
+ 666307205,
67
+ 773529912,
68
+ 1294757372,
69
+ 1396182291,
70
+ 1695183700,
71
+ 1986661051,
72
+ 2177026350,
73
+ 2456956037,
74
+ 2730485921,
75
+ 2820302411,
76
+ 3259730800,
77
+ 3345764771,
78
+ 3516065817,
79
+ 3600352804,
80
+ 4094571909,
81
+ 275423344,
82
+ 430227734,
83
+ 506948616,
84
+ 659060556,
85
+ 883997877,
86
+ 958139571,
87
+ 1322822218,
88
+ 1537002063,
89
+ 1747873779,
90
+ 1955562222,
91
+ 2024104815,
92
+ 2227730452,
93
+ 2361852424,
94
+ 2428436474,
95
+ 2756734187,
96
+ 3204031479,
97
+ 3329325298
98
+ ]);
99
+ var IV = [
100
+ 1779033703,
101
+ 3144134277,
102
+ 1013904242,
103
+ 2773480762,
104
+ 1359893119,
105
+ 2600822924,
106
+ 528734635,
107
+ 1541459225
108
+ ];
109
+ var W = new Uint32Array(64);
110
+ var rotr = (x, n) => x >>> n | x << 32 - n;
111
+ function compress(h, view, offset) {
112
+ for (let i = 0; i < 16; i++) W[i] = view.getUint32(offset + i * 4);
113
+ for (let i = 16; i < 64; i++) {
114
+ const a2 = W[i - 15];
115
+ const b2 = W[i - 2];
116
+ const s0 = rotr(a2, 7) ^ rotr(a2, 18) ^ a2 >>> 3;
117
+ const s1 = rotr(b2, 17) ^ rotr(b2, 19) ^ b2 >>> 10;
118
+ W[i] = W[i - 16] + s0 + W[i - 7] + s1 >>> 0;
119
+ }
120
+ let a = h[0], b = h[1], c = h[2], d = h[3], e = h[4], f = h[5], g = h[6], k = h[7];
121
+ for (let i = 0; i < 64; i++) {
122
+ const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
123
+ const ch = e & f ^ ~e & g;
124
+ const t1 = k + S1 + ch + K[i] + W[i] >>> 0;
125
+ const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
126
+ const maj = a & b ^ a & c ^ b & c;
127
+ const t2 = S0 + maj >>> 0;
128
+ k = g;
129
+ g = f;
130
+ f = e;
131
+ e = d + t1 >>> 0;
132
+ d = c;
133
+ c = b;
134
+ b = a;
135
+ a = t1 + t2 >>> 0;
136
+ }
137
+ h[0] = h[0] + a >>> 0;
138
+ h[1] = h[1] + b >>> 0;
139
+ h[2] = h[2] + c >>> 0;
140
+ h[3] = h[3] + d >>> 0;
141
+ h[4] = h[4] + e >>> 0;
142
+ h[5] = h[5] + f >>> 0;
143
+ h[6] = h[6] + g >>> 0;
144
+ h[7] = h[7] + k >>> 0;
145
+ }
146
+ function pad64(data) {
147
+ const padded = new Uint8Array(Math.ceil((data.length + 9) / 64) * 64);
148
+ padded.set(data);
149
+ padded[data.length] = 128;
150
+ const bits = data.length * 8;
151
+ const view = new DataView(padded.buffer);
152
+ view.setUint32(padded.length - 8, Math.floor(bits / 4294967296));
153
+ view.setUint32(padded.length - 4, bits >>> 0);
154
+ return view;
155
+ }
156
+ function wordsOut(h) {
157
+ const out = new Uint8Array(h.length * 4);
158
+ const view = new DataView(out.buffer);
159
+ for (let i = 0; i < h.length; i++) view.setUint32(i * 4, h[i]);
160
+ return out;
161
+ }
162
+ function sha256(data) {
163
+ const h = new Uint32Array(IV);
164
+ const view = pad64(data);
165
+ for (let offset = 0; offset < view.byteLength; offset += 64) compress(h, view, offset);
166
+ return wordsOut(h);
167
+ }
31
168
  var DEFAULT_XML_LIMITS = {
32
169
  maxDepth: 512,
33
170
  maxNodes: 5e6
@@ -278,11 +415,51 @@ function stripPageOverrides(xml) {
278
415
  (tag) => attrOf(tag, "ContentType") === PAGE_CONTENT_TYPE ? "" : tag
279
416
  );
280
417
  }
281
- function withPageOverrides(xml, pagePaths) {
282
- const clean = stripPageOverrides(xml);
283
- const add = pagePaths.map((path) => `<Override PartName="/${path}" ContentType="${PAGE_CONTENT_TYPE}"/>`).join("");
284
- const close = clean.lastIndexOf("</");
285
- return close < 0 ? clean : clean.slice(0, close) + add + clean.slice(close);
418
+ var withPageOverrides = (xml, pagePaths) => withOverrides(stripPageOverrides(xml), PAGE_CONTENT_TYPE, pagePaths);
419
+ var MASTER_TYPE = "http://schemas.microsoft.com/visio/2010/relationships/master";
420
+ var MASTER_CONTENT_TYPE = "application/vnd.ms-visio.master+xml";
421
+ function withOverrides(xml, contentType, paths) {
422
+ const add = paths.map((path) => `<Override PartName="/${path}" ContentType="${contentType}"/>`).join("");
423
+ const close = xml.lastIndexOf("</");
424
+ return close < 0 ? xml : xml.slice(0, close) + add + xml.slice(close);
425
+ }
426
+ function stripOverrides(xml, types, parts = /* @__PURE__ */ new Set()) {
427
+ return xml.replace(/<(?:\w+:)?Override\b[^>]*>/g, (tag) => {
428
+ const type = attrOf(tag, "ContentType");
429
+ const name = attrOf(tag, "PartName");
430
+ return type !== void 0 && types.includes(type) || name !== void 0 && parts.has(name.replace(/^\//, "")) ? "" : tag;
431
+ });
432
+ }
433
+ function declaredType(xml, path) {
434
+ for (const tag of xml.matchAll(/<(?:\w+:)?Override\b[^>]*>/g)) {
435
+ const name = attrOf(tag[0], "PartName");
436
+ const type = attrOf(tag[0], "ContentType");
437
+ if (name === `/${path}` && type !== void 0) return { type, byDefault: false };
438
+ }
439
+ const ext = /\.([^./]+)$/.exec(path)?.[1]?.toLowerCase();
440
+ if (ext === void 0) return void 0;
441
+ for (const tag of xml.matchAll(/<(?:\w+:)?Default\b[^>]*>/g)) {
442
+ const e = attrOf(tag[0], "Extension");
443
+ const type = attrOf(tag[0], "ContentType");
444
+ if (e?.toLowerCase() === ext && type !== void 0) return { type, byDefault: true };
445
+ }
446
+ return void 0;
447
+ }
448
+ function ensureType(xml, path, declared) {
449
+ if (declaredType(xml, path)) return xml;
450
+ const close = xml.lastIndexOf("</");
451
+ if (close < 0) return xml;
452
+ const ext = /\.([^./]+)$/.exec(path)?.[1];
453
+ const tag = declared.byDefault && ext ? `<Default Extension="${ext}" ContentType="${declared.type}"/>` : `<Override PartName="/${path}" ContentType="${declared.type}"/>`;
454
+ return xml.slice(0, close) + tag + xml.slice(close);
455
+ }
456
+ var isMediaPart = (path) => /(^|\/)media\/[^/]+$/i.test(path);
457
+ function relativeTarget(dir, path) {
458
+ const from = dir ? dir.split("/") : [];
459
+ const to = path.split("/");
460
+ let i = 0;
461
+ while (i < from.length && i < to.length - 1 && from[i] === to[i]) i++;
462
+ return [...from.slice(i).map(() => ".."), ...to.slice(i)].join("/");
286
463
  }
287
464
  function toBase64(bytes) {
288
465
  let out = "";
@@ -316,15 +493,154 @@ function canon(xml) {
316
493
  canonCache.set(xml, result);
317
494
  return result;
318
495
  }
496
+ function contentHash(bytes) {
497
+ let out = "";
498
+ for (const byte of sha256(bytes)) out += byte.toString(16).padStart(2, "0");
499
+ return out;
500
+ }
501
+
502
+ // visio/collab/pieces.ts
503
+ var seg = (value) => value.replace(/[%/]/g, (c) => c === "%" ? "%25" : "%2F");
504
+ var tagName = (open) => /^<([^\s/>]+)/.exec(open)?.[1] ?? "Shape";
505
+ var openTagOf = (text, span) => {
506
+ const tag = text.slice(span.start, span.openEnd);
507
+ return span.selfClosing ? tag.replace(/\s*\/>$/, ">") : tag;
508
+ };
509
+ var closeTagOf = (open) => `</${tagName(open)}>`;
510
+ function splitShape(xml) {
511
+ const box = scanContainer(xml, "Shape");
512
+ if (!box) return void 0;
513
+ const used = /* @__PURE__ */ new Set();
514
+ const unique = (path) => {
515
+ let out = path;
516
+ for (let n = 2; used.has(out); n++) out = `${path}~${n}`;
517
+ used.add(out);
518
+ return out;
519
+ };
520
+ const pieces = [];
521
+ const nested = [];
522
+ const others = /* @__PURE__ */ new Map();
523
+ const other = (parent, name, text) => {
524
+ const k = others.get(parent + name) ?? 0;
525
+ others.set(parent + name, k + 1);
526
+ pieces.push({
527
+ path: unique(`${parent ? `${parent}/` : ""}x/${name}#${k}`),
528
+ parent,
529
+ kind: "leaf",
530
+ text
531
+ });
532
+ };
533
+ const cell = (parent, text) => {
534
+ const n = attrOf(text.slice(0, text.search(/>/) + 1), "N");
535
+ if (n === void 0) return other(parent, "Cell", text);
536
+ pieces.push({
537
+ path: unique(`${parent ? `${parent}/` : ""}c/${seg(n)}`),
538
+ parent,
539
+ kind: "leaf",
540
+ text
541
+ });
542
+ };
543
+ for (const child of box.children) {
544
+ const text = xml.slice(child.start, child.end);
545
+ if (child.name === "Cell") cell("", text);
546
+ else if (child.name === "Shapes") {
547
+ const list = scanContainer(text, "Shapes");
548
+ for (const s of list?.children ?? [])
549
+ if (s.name === "Shape") nested.push(text.slice(s.start, s.end));
550
+ } else if (child.name === "Section") {
551
+ const head = openTagOf(xml, child);
552
+ const n = attrOf(head, "N");
553
+ const ix = attrOf(head, "IX");
554
+ if (n === void 0 && ix === void 0) {
555
+ other("", "Section", text);
556
+ continue;
557
+ }
558
+ const sec = unique(`s/${seg(n ?? "")}${ix !== void 0 ? `:${seg(ix)}` : ""}`);
559
+ pieces.push({ path: sec, parent: "", kind: "open", text: head });
560
+ const inner = scanContainer(text, "Section");
561
+ let rowIndex = 0;
562
+ for (const part of inner?.children ?? []) {
563
+ const sub = text.slice(part.start, part.end);
564
+ if (part.name === "Cell") cell(sec, sub);
565
+ else if (part.name === "Row") {
566
+ const rowHead = openTagOf(text, part);
567
+ const id = attrOf(rowHead, "IX") ?? attrOf(rowHead, "N") ?? `#${rowIndex}`;
568
+ rowIndex++;
569
+ const row = unique(`${sec}/r/${seg(id)}`);
570
+ pieces.push({ path: row, parent: sec, kind: "open", text: rowHead });
571
+ for (const rc of scanContainer(sub, "Row")?.children ?? [])
572
+ if (rc.name === "Cell") cell(row, sub.slice(rc.start, rc.end));
573
+ else other(row, rc.name, sub.slice(rc.start, rc.end));
574
+ } else other(sec, part.name, sub);
575
+ }
576
+ } else other("", child.name, text);
577
+ }
578
+ return { open: openTagOf(xml, box), pieces, nested };
579
+ }
580
+ function buildShape(open, entries, tail) {
581
+ const byParent = /* @__PURE__ */ new Map();
582
+ for (const [path, entry] of entries) {
583
+ const parent = parentOf(path);
584
+ const list = byParent.get(parent) ?? [];
585
+ list.push({ path, entry });
586
+ byParent.set(parent, list);
587
+ }
588
+ const render = (parent) => (byParent.get(parent) ?? []).sort((a, b) => a.entry.pos - b.entry.pos || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)).map(
589
+ ({ path, entry }) => entry.open !== void 0 ? entry.open + render(path) + closeTagOf(entry.open) : entry.xml ?? ""
590
+ ).join("");
591
+ return open + render("") + tail + closeTagOf(open);
592
+ }
593
+ function parentOf(path) {
594
+ const parts = path.split("/");
595
+ return parts.length <= 2 ? "" : parts.slice(0, -2).join("/");
596
+ }
597
+ var SIMPLE = /^<([^\s/>]+)((?:\s+[^\s=/>]+\s*=\s*(?:"[^"]*"|'[^']*'))*)\s*(?:\/>|>([^<]*)<\/\1>)$/;
598
+ var ATTR = /\s([^\s=/>]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
599
+ function pieceContent(kind, text) {
600
+ const simple = SIMPLE.exec(kind === "open" ? `${text}</${tagName(text)}>` : text);
601
+ if (!simple) return kind === "open" ? canon(`${text}</${tagName(text)}>`) : canon(text);
602
+ const attrs = [...(simple[2] ?? "").matchAll(ATTR)].map((m) => [m[1], m[2] ?? m[3] ?? ""]).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
603
+ return JSON.stringify([simple[1], attrs, simple[3] ?? ""]);
604
+ }
605
+
606
+ // visio/collab/baseline.ts
607
+ var masterScope = (key) => `m~${key}`;
608
+ var pageId = (key) => `p:${key}`;
609
+ var bodyId = (key) => `b:${key}`;
610
+ var shapeId = (page, key) => `s:${page}/${key}`;
611
+ var cellId = (page, shape, path) => `f:${page}/${shape}/${path}`;
612
+ var connectId = (key) => `c:${key}`;
613
+ var masterId = (key) => `m:${key}`;
614
+ var mediaId = (key) => `i:${key}`;
615
+ var pageContent = (e) => JSON.stringify([e.id, e.back ?? null, canon(e.xml)]);
616
+ var bodyContent = (e) => JSON.stringify([canon(e.shell), e.rels ?? null]);
617
+ var shellContent = (e) => JSON.stringify([e.parent ?? null, pieceContent("open", e.open)]);
618
+ var entryContent = (e) => e.xml !== void 0 ? pieceContent("leaf", e.xml) : pieceContent("open", e.open ?? "");
619
+ var masterContent = (e) => JSON.stringify([e.id, canon(e.xml), e.rels ?? null, e.print]);
319
620
 
320
621
  // visio/collab/schema.ts
321
- var SCHEMA_VERSION = 1;
622
+ var SCHEMA_VERSION = 3;
623
+ var SchemaVersionError = class extends Error {
624
+ constructor(found) {
625
+ super(
626
+ `The shared drawing uses schema version ${String(found)}; this build reads version ${SCHEMA_VERSION}.`
627
+ );
628
+ this.found = found;
629
+ this.name = "SchemaVersionError";
630
+ }
631
+ found;
632
+ };
322
633
  var META = "visio:meta";
323
634
  var PAGES = "visio:pages";
324
635
  var BODIES = "visio:bodies";
325
636
  var SHAPES = "visio:shapes";
637
+ var CELLS = "visio:cells";
326
638
  var CONNECTS = "visio:connects";
327
639
  var DEAD = "visio:dead";
640
+ var MASTERS = "visio:masters";
641
+ var RENUM = "visio:renum";
642
+ var MEDIA = "visio:media";
643
+ var BLOBS = "visio:blobs";
328
644
  var PARTS = "visio:parts";
329
645
  var REL_MARK = "<!--@rel-->";
330
646
  var SHAPES_MARK = "<!--@shapes-->";
@@ -335,8 +651,13 @@ function sharedDrawing(doc) {
335
651
  pages: doc.getMap(PAGES),
336
652
  bodies: doc.getMap(BODIES),
337
653
  shapes: doc.getMap(SHAPES),
654
+ cells: doc.getMap(CELLS),
338
655
  connects: doc.getMap(CONNECTS),
339
656
  dead: doc.getMap(DEAD),
657
+ masters: doc.getMap(MASTERS),
658
+ renum: doc.getMap(RENUM),
659
+ media: doc.getMap(MEDIA),
660
+ blobs: doc.getMap(BLOBS),
340
661
  parts: doc.getMap(PARTS)
341
662
  };
342
663
  }
@@ -345,17 +666,27 @@ var sharedTypes = (shared) => [
345
666
  shared.pages,
346
667
  shared.bodies,
347
668
  shared.shapes,
669
+ shared.cells,
348
670
  shared.connects,
349
671
  shared.dead,
672
+ shared.masters,
673
+ shared.renum,
674
+ shared.media,
675
+ shared.blobs,
350
676
  shared.parts
351
677
  ];
352
678
  var isSharedDrawingEmpty = (shared) => shared.pages.size === 0 && shared.meta.get("v") === void 0;
679
+ function assertSchemaVersion(shared) {
680
+ const v = shared.meta.get("v");
681
+ if (v !== void 0 && v !== SCHEMA_VERSION) throw new SchemaVersionError(v);
682
+ }
353
683
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
354
684
  function sameRecord(a, b) {
355
685
  if (!isRecord(a) || !isRecord(b)) return a === b;
356
686
  const ka = Object.keys(a).filter((k) => a[k] !== void 0);
357
687
  const kb = Object.keys(b).filter((k) => b[k] !== void 0);
358
- return ka.length === kb.length && ka.every((k) => a[k] === b[k]);
688
+ const same = (x, y) => x === y || Array.isArray(x) && Array.isArray(y) && JSON.stringify(x) === JSON.stringify(y);
689
+ return ka.length === kb.length && ka.every((k) => same(a[k], b[k]));
359
690
  }
360
691
  function setIfChanged(map, key, value) {
361
692
  if (sameRecord(map.get(key), value)) return false;
@@ -364,27 +695,53 @@ function setIfChanged(map, key, value) {
364
695
  }
365
696
  var str = (value) => typeof value === "string" ? value : void 0;
366
697
  var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
698
+ var opt = (key, value) => value === void 0 ? {} : { [key]: value };
367
699
  function pageEntry(value) {
368
700
  if (!isRecord(value)) return void 0;
369
701
  const pos = num(value.pos);
370
702
  const id = str(value.id);
371
703
  const xml = str(value.xml);
372
704
  if (pos === void 0 || id === void 0 || xml === void 0) return void 0;
373
- const back = str(value.back);
374
- return { pos, id, xml, ...back !== void 0 ? { back } : {} };
705
+ return { pos, id, xml, ...opt("back", str(value.back)) };
375
706
  }
376
707
  function bodyEntry(value) {
377
708
  if (!isRecord(value)) return void 0;
378
709
  const shell = str(value.shell);
379
710
  if (shell === void 0) return void 0;
380
- const rels = str(value.rels);
381
- return { shell, ...rels !== void 0 ? { rels } : {} };
711
+ return { shell, ...opt("rels", str(value.rels)), ...opt("b", str(value.b)) };
382
712
  }
383
713
  function shapeEntry(value) {
714
+ if (!isRecord(value)) return void 0;
715
+ const pos = num(value.pos);
716
+ const open = str(value.open);
717
+ if (pos === void 0 || open === void 0) return void 0;
718
+ return { pos, open, ...opt("parent", str(value.parent)), ...opt("b", str(value.b)) };
719
+ }
720
+ function cellEntry(value) {
384
721
  if (!isRecord(value)) return void 0;
385
722
  const pos = num(value.pos);
386
723
  const xml = str(value.xml);
387
- return pos === void 0 || xml === void 0 ? void 0 : { pos, xml };
724
+ const open = str(value.open);
725
+ if (pos === void 0 || xml === void 0 && open === void 0) return void 0;
726
+ return { pos, ...opt("xml", xml), ...opt("open", xml === void 0 ? open : void 0) };
727
+ }
728
+ function masterEntry(value) {
729
+ if (!isRecord(value)) return void 0;
730
+ const pos = num(value.pos);
731
+ const id = str(value.id);
732
+ const xml = str(value.xml);
733
+ const part = str(value.part);
734
+ if (pos === void 0 || id === void 0 || xml === void 0 || part === void 0)
735
+ return void 0;
736
+ return { pos, id, xml, part, ...opt("rels", str(value.rels)), ...opt("b", str(value.b)) };
737
+ }
738
+ function mediaEntry(value) {
739
+ if (!isRecord(value)) return void 0;
740
+ const path = str(value.path);
741
+ const hash = str(value.hash);
742
+ const ct = str(value.ct);
743
+ if (path === void 0 || hash === void 0 || ct === void 0) return void 0;
744
+ return { path, hash, ct, d: value.d === true, ...opt("b", str(value.b)) };
388
745
  }
389
746
  function connectEntry(value) {
390
747
  if (!isRecord(value)) return void 0;
@@ -396,11 +753,15 @@ function connectEntry(value) {
396
753
  }
397
754
  return out;
398
755
  }
399
- var DEFAULT_MAX_PART_BYTES = 48 * 1024 * 1024;
400
- var SHAPE_ID = /<(?:\w+:)?Shape\s[^>]*?\bID\s*=\s*(["'])(\d+)\1/g;
401
- function shapeIds(xml) {
402
- return [...xml.matchAll(SHAPE_ID)].map((m) => m[2]);
756
+ function renumEntry(value) {
757
+ if (!isRecord(value)) return void 0;
758
+ const pair = (v) => Array.isArray(v) && v.length === 2 && typeof v[0] === "string" && typeof v[1] === "string" ? [v[0], v[1]] : void 0;
759
+ const i = pair(value.i);
760
+ const m = pair(value.m);
761
+ return i || m ? { ...i ? { i } : {}, ...m ? { m } : {} } : void 0;
403
762
  }
763
+
764
+ // visio/collab/snapshot-contents.ts
404
765
  function splitList(text, name, mark, child) {
405
766
  const box = scanContainer(text, name);
406
767
  if (!box) return { text, items: [] };
@@ -416,6 +777,46 @@ function splitList(text, name, mark, child) {
416
777
  }
417
778
  return { text: text.slice(0, box.openEnd) + mark + text.slice(box.closeStart), items };
418
779
  }
780
+ function flatten(xml, parent, page, keys, out) {
781
+ const split2 = splitShape(xml);
782
+ const id = split2 ? attrOf(split2.open, "ID") : void 0;
783
+ if (!split2 || id === void 0) return;
784
+ const key = keys.shapeKey(page, id);
785
+ out.push({
786
+ key,
787
+ id,
788
+ ...parent !== void 0 ? { parent } : {},
789
+ open: split2.open,
790
+ pieces: split2.pieces
791
+ });
792
+ for (const nested of split2.nested) flatten(nested, key, page, keys, out);
793
+ }
794
+ function splitContents(content, scope, keys) {
795
+ const shapesSplit = splitList(content, "Shapes", SHAPES_MARK, "Shape");
796
+ const connectsSplit = splitList(shapesSplit.text, "Connects", CONNECTS_MARK, "Connect");
797
+ const shapes = [];
798
+ for (const text of shapesSplit.items) flatten(text, void 0, scope, keys, shapes);
799
+ const owner = new Map(shapes.map((s) => [s.id, s.key]));
800
+ const connects = [];
801
+ for (const text of connectsSplit.items) {
802
+ const a = (name) => attrOf(text, name) ?? "";
803
+ const entry = {
804
+ fk: owner.get(a("FromSheet")) ?? "",
805
+ fi: a("FromSheet"),
806
+ tk: owner.get(a("ToSheet")) ?? "",
807
+ ti: a("ToSheet"),
808
+ fc: a("FromCell"),
809
+ tc: a("ToCell"),
810
+ fp: a("FromPart"),
811
+ tp: a("ToPart")
812
+ };
813
+ connects.push({
814
+ key: `${scope}/${entry.fk}.${entry.fi}.${entry.fc}.${entry.fp}>${entry.tk}.${entry.ti}.${entry.tc}.${entry.tp}`,
815
+ entry
816
+ });
817
+ }
818
+ return { shell: connectsSplit.text, shapes, connects };
819
+ }
419
820
  var readZip = (bytes) => JSZip__default.default.loadAsync(bytes);
420
821
  var zipText = async (zip, path) => zip.file(path)?.async("string");
421
822
  async function readLayout(zip) {
@@ -426,19 +827,25 @@ async function readLayout(zip) {
426
827
  const documentPath = resolveTarget("", root.target);
427
828
  const rels = parseRelationships(await zipText(zip, relsPathOf(documentPath)));
428
829
  const pages = rels.find((r) => r.type.endsWith("/pages"));
429
- return pages ? { documentPath, pagesPath: resolveTarget(dirName(documentPath), pages.target) } : void 0;
830
+ const masters = rels.find((r) => r.type.endsWith("/masters"));
831
+ const dir = dirName(documentPath);
832
+ return pages ? {
833
+ documentPath,
834
+ pagesPath: resolveTarget(dir, pages.target),
835
+ ...masters ? { mastersPath: resolveTarget(dir, masters.target) } : {}
836
+ } : void 0;
430
837
  }
431
- async function readLocalPages(zip, layout, pagesXml) {
432
- const box = scanContainer(pagesXml, "Pages");
838
+ async function readLocalItems(zip, listPath, xml, container, child) {
839
+ const box = scanContainer(xml, container);
433
840
  if (!box) return void 0;
434
841
  const rels = new Map(
435
- parseRelationships(await zipText(zip, relsPathOf(layout.pagesPath))).map((r) => [r.id, r])
842
+ parseRelationships(await zipText(zip, relsPathOf(listPath))).map((r) => [r.id, r])
436
843
  );
437
- const pages = [];
844
+ const items = [];
438
845
  for (const span of box.children) {
439
- if (span.name !== "Page") continue;
440
- const text = pagesXml.slice(span.start, span.end);
441
- const inner = scanContainer(text, "Page");
846
+ if (span.name !== child) continue;
847
+ const text = xml.slice(span.start, span.end);
848
+ const inner = scanContainer(text, child);
442
849
  const relSpan = inner?.children.find((c) => c.name === "Rel");
443
850
  if (!inner || !relSpan) return void 0;
444
851
  const relTag = text.slice(relSpan.start, relSpan.openEnd);
@@ -446,239 +853,194 @@ async function readLocalPages(zip, layout, pagesXml) {
446
853
  const target = rid ? rels.get(rid)?.target : void 0;
447
854
  const id = attrOf(text.slice(0, inner.openEnd), "ID");
448
855
  if (target === void 0 || id === void 0) return void 0;
449
- pages.push({
856
+ items.push({
450
857
  id,
451
858
  text,
452
- path: resolveTarget(dirName(layout.pagesPath), target),
859
+ path: resolveTarget(dirName(listPath), target),
453
860
  rel: { start: relSpan.start, end: relSpan.end }
454
861
  });
455
862
  }
456
- return { box, pages };
863
+ return { box, items };
864
+ }
865
+ var withMark = (item) => item.text.slice(0, item.rel.start) + REL_MARK + item.text.slice(item.rel.end);
866
+
867
+ // visio/collab/snapshot-parts.ts
868
+ async function snapshotMasters(zip, layout, keys, derived) {
869
+ const masters = [];
870
+ let mastersOpen;
871
+ let mastersClose;
872
+ if (layout.mastersPath) {
873
+ const listXml = await zipText(zip, layout.mastersPath) ?? "";
874
+ const list = await readLocalItems(zip, layout.mastersPath, listXml, "Masters", "Master");
875
+ if (list) {
876
+ derived.add(layout.mastersPath).add(relsPathOf(layout.mastersPath));
877
+ mastersOpen = listXml.slice(0, list.box.openEnd);
878
+ mastersClose = listXml.slice(list.box.closeStart);
879
+ for (const item of list.items) {
880
+ const content = await zipText(zip, item.path);
881
+ if (content === void 0) continue;
882
+ derived.add(item.path).add(relsPathOf(item.path));
883
+ const key = keys.masterKey(item.id);
884
+ keys.masterPaths.set(key, item.path);
885
+ const rels = await zipText(zip, relsPathOf(item.path));
886
+ const { shell, shapes, connects } = splitContents(content, masterScope(key), keys);
887
+ masters.push({
888
+ key,
889
+ id: item.id,
890
+ xml: withMark(item),
891
+ path: item.path,
892
+ part: encoder.encode(shell),
893
+ ...rels !== void 0 ? { rels } : {},
894
+ shapes,
895
+ connects
896
+ });
897
+ }
898
+ }
899
+ }
900
+ return { masters, mastersOpen, mastersClose };
901
+ }
902
+ async function snapshotRest(zip, derived, limit) {
903
+ const parts = /* @__PURE__ */ new Map();
904
+ const media = [];
905
+ const typesXml = await zipText(zip, CONTENT_TYPES) ?? "";
906
+ const mediaPaths = /* @__PURE__ */ new Set();
907
+ let total = 0;
908
+ for (const path of Object.keys(zip.files)) {
909
+ const file = zip.files[path];
910
+ if (file.dir || derived.has(path) || isLocalOnlyPart(path)) continue;
911
+ const data = await file.async("uint8array");
912
+ total += data.length;
913
+ if (total > limit) throw new Error("The package parts exceed the shared size limit.");
914
+ const declared = isMediaPart(path) ? declaredType(typesXml, path) : void 0;
915
+ if (declared) {
916
+ mediaPaths.add(path);
917
+ media.push({
918
+ path,
919
+ hash: contentHash(data),
920
+ ct: declared.type,
921
+ d: declared.byDefault,
922
+ data
923
+ });
924
+ } else parts.set(path, data);
925
+ }
926
+ const types = parts.get(CONTENT_TYPES);
927
+ if (types)
928
+ parts.set(
929
+ CONTENT_TYPES,
930
+ encoder.encode(
931
+ stripOverrides(decoder.decode(types), [PAGE_CONTENT_TYPE, MASTER_CONTENT_TYPE], mediaPaths)
932
+ )
933
+ );
934
+ return { parts, media };
457
935
  }
936
+
937
+ // visio/collab/snapshot.ts
938
+ var DEFAULT_MAX_PART_BYTES = 48 * 1024 * 1024;
458
939
  async function snapshotDrawing(bytes, keys, options = {}) {
459
940
  const zip = await readZip(bytes);
460
941
  const layout = await readLayout(zip);
461
942
  if (!layout) throw new Error("The package has no Visio pages part.");
462
943
  const pagesXml = await zipText(zip, layout.pagesPath) ?? "";
463
- const local = await readLocalPages(zip, layout, pagesXml);
944
+ const local = await readLocalItems(zip, layout.pagesPath, pagesXml, "Pages", "Page");
464
945
  if (!local) throw new Error("The pages part cannot be split into pages.");
465
946
  const derived = /* @__PURE__ */ new Set([layout.pagesPath, relsPathOf(layout.pagesPath)]);
466
- const idToKey = new Map(local.pages.map((p) => [p.id, keys.pageKey(p.id)]));
947
+ const idToKey = new Map(local.items.map((p) => [p.id, keys.pageKey(p.id)]));
467
948
  const pages = [];
468
- for (const page of local.pages) {
949
+ for (const page of local.items) {
469
950
  const key = idToKey.get(page.id);
470
951
  derived.add(page.path).add(relsPathOf(page.path));
471
952
  const head = page.text.slice(0, scanContainer(page.text, "Page").openEnd);
472
953
  const backId = attrOf(head, "BackPage");
473
- const xml = page.text.slice(0, page.rel.start) + REL_MARK + page.text.slice(page.rel.end);
474
954
  const content = await zipText(zip, page.path) ?? "";
475
955
  const rels = await zipText(zip, relsPathOf(page.path));
476
- const shapesSplit = splitList(content, "Shapes", SHAPES_MARK, "Shape");
477
- const connectsSplit = splitList(shapesSplit.text, "Connects", CONNECTS_MARK, "Connect");
478
- const owner = /* @__PURE__ */ new Map();
479
- const shapes = [];
480
- for (const text of shapesSplit.items) {
481
- const ids = shapeIds(text);
482
- const id = ids[0];
483
- if (id === void 0) continue;
484
- const shapeKey = keys.shapeKey(key, id);
485
- for (const nested of ids) owner.set(nested, shapeKey);
486
- shapes.push({ key: shapeKey, id, xml: text });
487
- }
488
- const connects = [];
489
- for (const text of connectsSplit.items) {
490
- const a = (name) => attrOf(text, name) ?? "";
491
- const entry = {
492
- fk: owner.get(a("FromSheet")) ?? "",
493
- fi: a("FromSheet"),
494
- tk: owner.get(a("ToSheet")) ?? "",
495
- ti: a("ToSheet"),
496
- fc: a("FromCell"),
497
- tc: a("ToCell"),
498
- fp: a("FromPart"),
499
- tp: a("ToPart")
500
- };
501
- connects.push({
502
- key: `${key}/${entry.fk}.${entry.fi}.${entry.fc}.${entry.fp}>${entry.tk}.${entry.ti}.${entry.tc}.${entry.tp}`,
503
- entry
504
- });
505
- }
956
+ const { shell, shapes, connects } = splitContents(content, key, keys);
506
957
  keys.pagePaths.set(key, page.path);
507
958
  pages.push({
508
959
  key,
509
960
  id: page.id,
510
961
  ...backId !== void 0 && idToKey.has(backId) ? { back: idToKey.get(backId) } : {},
511
- xml,
512
- shell: connectsSplit.text,
962
+ xml: withMark(page),
963
+ shell,
513
964
  ...rels !== void 0 ? { rels } : {},
514
965
  path: page.path,
515
966
  shapes,
516
967
  connects
517
968
  });
518
969
  }
519
- const parts = /* @__PURE__ */ new Map();
520
- let total = 0;
521
- const limit = options.maxPartBytes ?? DEFAULT_MAX_PART_BYTES;
522
- for (const path of Object.keys(zip.files)) {
523
- const file = zip.files[path];
524
- if (file.dir || derived.has(path) || isLocalOnlyPart(path)) continue;
525
- let data = await file.async("uint8array");
526
- if (path === CONTENT_TYPES) data = encoder.encode(stripPageOverrides(decoder.decode(data)));
527
- total += data.length;
528
- if (total > limit) throw new Error("The package parts exceed the shared size limit.");
529
- parts.set(path, data);
530
- }
970
+ const { masters, mastersOpen, mastersClose } = await snapshotMasters(zip, layout, keys, derived);
971
+ const { parts, media } = await snapshotRest(
972
+ zip,
973
+ derived,
974
+ options.maxPartBytes ?? DEFAULT_MAX_PART_BYTES
975
+ );
531
976
  return {
532
977
  pagesPath: layout.pagesPath,
533
978
  pagesOpen: pagesXml.slice(0, local.box.openEnd),
534
979
  pagesClose: pagesXml.slice(local.box.closeStart),
535
980
  pages,
981
+ ...mastersOpen !== void 0 && mastersClose !== void 0 && layout.mastersPath ? { mastersPath: layout.mastersPath, mastersOpen, mastersClose } : {},
982
+ masters,
983
+ media,
536
984
  parts
537
985
  };
538
986
  }
539
987
 
540
- // visio/collab/order.ts
541
- function assignPositions(order, current) {
542
- const result = /* @__PURE__ */ new Map();
543
- const kept = [];
544
- let last = -Infinity;
545
- for (const key of order) {
546
- const pos = current(key);
547
- if (pos !== void 0 && pos > last) {
548
- kept.push(pos);
549
- last = pos;
550
- } else kept.push(void 0);
551
- }
552
- let i = 0;
553
- while (i < order.length) {
554
- const known = kept[i];
555
- if (known !== void 0) {
556
- result.set(order[i], known);
557
- i++;
558
- continue;
559
- }
560
- let end = i;
561
- while (end < order.length && kept[end] === void 0) end++;
562
- const lo = i > 0 ? kept[i - 1] : void 0;
563
- const hi = end < order.length ? kept[end] : void 0;
564
- const count = end - i;
565
- for (let j = 0; j < count; j++) {
566
- const key = order[i + j];
567
- if (lo !== void 0 && hi !== void 0)
568
- result.set(key, lo + (hi - lo) * (j + 1) / (count + 1));
569
- else if (lo !== void 0) result.set(key, lo + j + 1);
570
- else if (hi !== void 0) result.set(key, hi - count + j);
571
- else result.set(key, j + 1);
572
- }
573
- i = end;
574
- }
575
- return result;
576
- }
577
- function byPosition(items) {
578
- return items.sort((a, b) => a.pos - b.pos || (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
579
- }
580
-
581
- // visio/collab/write.ts
582
- var pageId = (key) => `p:${key}`;
583
- var bodyId = (key) => `b:${key}`;
584
- var shapeId = (page, key) => `s:${page}/${key}`;
585
- var connectId = (key) => `c:${key}`;
586
- var pageContent = (e) => JSON.stringify([e.id, e.back ?? null, canon(e.xml)]);
587
- var bodyContent = (e) => JSON.stringify([canon(e.shell), e.rels ?? null]);
588
- function writeSharedDrawing(shared, snapshot, keys) {
589
- let writes = 0;
590
- const set = (map, key, value) => {
591
- if (setIfChanged(map, key, value)) writes++;
592
- };
593
- set(shared.meta, "v", SCHEMA_VERSION);
594
- set(shared.meta, "pagesPath", snapshot.pagesPath);
595
- set(shared.meta, "pagesOpen", snapshot.pagesOpen);
596
- set(shared.meta, "pagesClose", snapshot.pagesClose);
597
- const now = /* @__PURE__ */ new Map();
598
- const changed = (id, content, room) => {
599
- const base = keys.known.get(id);
600
- return base === void 0 ? room !== content : base !== content;
601
- };
602
- const pagePos = assignPositions(
603
- snapshot.pages.map((p) => p.key),
604
- (key) => pageEntry(shared.pages.get(key))?.pos
605
- );
606
- for (const page of snapshot.pages) {
607
- const room = pageEntry(shared.pages.get(page.key));
608
- const content = pageContent(page);
609
- now.set(pageId(page.key), content);
610
- const own = changed(pageId(page.key), content, room && pageContent(room));
611
- const source = own || !room ? page : room;
612
- set(shared.pages, page.key, {
613
- pos: pagePos.get(page.key),
614
- id: source.id,
615
- ...source.back !== void 0 ? { back: source.back } : {},
616
- xml: source.xml
617
- });
618
- const roomBody = bodyEntry(shared.bodies.get(page.key));
619
- const bodyText = bodyContent(page);
620
- now.set(bodyId(page.key), bodyText);
621
- const ownBody = changed(bodyId(page.key), bodyText, roomBody && bodyContent(roomBody));
622
- const body = ownBody || !roomBody ? page : roomBody;
623
- set(shared.bodies, page.key, {
624
- shell: body.shell,
625
- ...body.rels !== void 0 ? { rels: body.rels } : {}
626
- });
627
- const shapePos = assignPositions(
628
- page.shapes.map((s) => s.key),
629
- (key) => shapeEntry(shared.shapes.get(`${page.key}/${key}`))?.pos
630
- );
631
- for (const shape of page.shapes) {
632
- const id = shapeId(page.key, shape.key);
633
- const roomShape = shapeEntry(shared.shapes.get(`${page.key}/${shape.key}`));
634
- const text = canon(shape.xml);
635
- now.set(id, text);
636
- const xml = changed(id, text, roomShape && canon(roomShape.xml)) || !roomShape ? shape.xml : roomShape.xml;
637
- set(shared.shapes, `${page.key}/${shape.key}`, { pos: shapePos.get(shape.key), xml });
638
- }
639
- for (const connect of page.connects) {
640
- now.set(connectId(connect.key), "");
641
- if (!keys.known.has(connectId(connect.key)) || !shared.connects.has(connect.key))
642
- set(shared.connects, connect.key, connect.entry);
988
+ // visio/collab/compose-lists.ts
989
+ var relTarget = (dir, path) => dir && path.startsWith(`${dir}/`) ? path.slice(dir.length + 1) : `/${path}`;
990
+ async function placeList(options) {
991
+ const { files, taken, localZip, items } = options;
992
+ const dir = dirName(options.listPath);
993
+ taken.add(options.listPath).add(relsPathOf(options.listPath));
994
+ const paths = /* @__PURE__ */ new Map();
995
+ for (const item of items) {
996
+ const known = options.known.get(item.key);
997
+ if (known && !taken.has(known) && localZip?.file(known)) {
998
+ paths.set(item.key, known);
999
+ taken.add(known);
643
1000
  }
644
1001
  }
645
- for (const id of keys.known.keys()) {
646
- if (now.has(id)) continue;
647
- const name = id.slice(2);
648
- if (id.startsWith("p:")) {
649
- shared.dead.set(name, true);
650
- for (const map of [shared.pages, shared.bodies]) map.delete(name);
651
- for (const map of [shared.shapes, shared.connects])
652
- for (const key of [...map.keys()]) if (key.startsWith(`${name}/`)) map.delete(key);
653
- keys.forgetPage(name);
654
- writes++;
655
- } else if (id.startsWith("s:")) {
656
- const slash = name.indexOf("/");
657
- const page = name.slice(0, slash);
658
- if (!now.has(pageId(page))) continue;
659
- shared.dead.set(name, true);
660
- shared.shapes.delete(name);
661
- keys.forgetShape(page, name.slice(slash + 1));
662
- writes++;
663
- } else if (id.startsWith("c:") && shared.connects.has(name)) {
664
- shared.connects.delete(name);
665
- writes++;
666
- }
1002
+ let counter = 0;
1003
+ for (const item of items) {
1004
+ if (paths.has(item.key)) continue;
1005
+ let path;
1006
+ do
1007
+ path = `${dir ? `${dir}/` : ""}${options.stem}${++counter}.xml`;
1008
+ while (taken.has(path) || localZip?.file(path));
1009
+ paths.set(item.key, path);
1010
+ taken.add(path);
667
1011
  }
668
- keys.known = now;
669
- for (const [path, data] of snapshot.parts) {
670
- const print = fingerprint(data);
671
- if (keys.partPrints.get(path) === print) continue;
672
- set(shared.parts, path, toBase64(data));
673
- keys.partPrints.set(path, print);
1012
+ const others = parseRelationships(
1013
+ localZip ? await zipText(localZip, relsPathOf(options.listPath)) : void 0
1014
+ ).filter((r) => r.type !== options.relType);
1015
+ const usedIds = new Set(others.map((r) => r.id));
1016
+ let nextRel = 0;
1017
+ const rels = [];
1018
+ const elements = [];
1019
+ for (const item of items) {
1020
+ const path = paths.get(item.key);
1021
+ let id;
1022
+ do
1023
+ id = `rId${++nextRel}`;
1024
+ while (usedIds.has(id));
1025
+ usedIds.add(id);
1026
+ rels.push(
1027
+ `<Relationship Id="${id}" Type="${options.relType}" Target="${escapeAttr(relTarget(dir, path))}"/>`
1028
+ );
1029
+ const rel = `<Rel xmlns:r="${REL_NS}" r:id="${id}"/>`;
1030
+ elements.push(
1031
+ item.xml.includes(REL_MARK) ? item.xml.replace(REL_MARK, () => rel) : item.xml.slice(0, item.xml.lastIndexOf("</")) + rel + item.xml.slice(item.xml.lastIndexOf("</"))
1032
+ );
1033
+ files.set(path, item.part);
1034
+ if (item.rels !== void 0) files.set(relsPathOf(path), encoder.encode(item.rels));
674
1035
  }
675
- for (const path of [...keys.partPrints.keys()])
676
- if (!snapshot.parts.has(path)) {
677
- shared.parts.delete(path);
678
- keys.partPrints.delete(path);
679
- writes++;
680
- }
681
- return writes;
1036
+ files.set(options.listPath, encoder.encode(options.open + elements.join("") + options.close));
1037
+ files.set(
1038
+ relsPathOf(options.listPath),
1039
+ encoder.encode(
1040
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' + others.map((r) => r.text).join("") + rels.join("") + "</Relationships>"
1041
+ )
1042
+ );
1043
+ return paths;
682
1044
  }
683
1045
 
684
1046
  // visio/collab/compose.ts
@@ -694,32 +1056,51 @@ function connectXml(item) {
694
1056
  ];
695
1057
  return `<Connect ${attrs.filter(([, v]) => v !== "").map(([k, v]) => `${k}="${escapeAttr(v)}"`).join(" ")}/>`;
696
1058
  }
697
- function pagePartXml(page) {
698
- let shell = page.body.shell;
699
- const root = scanContainer(shell, "PageContents");
700
- if (page.shapes.length && !shell.includes(SHAPES_MARK) && root && !root.selfClosing)
701
- shell = shell.slice(0, root.openEnd) + `<Shapes>${SHAPES_MARK}</Shapes>` + shell.slice(root.openEnd);
702
- const root2 = scanContainer(shell, "PageContents");
703
- if (page.connects.length && !shell.includes(CONNECTS_MARK) && root2 && !root2.selfClosing)
704
- shell = shell.slice(0, root2.closeStart) + `<Connects>${CONNECTS_MARK}</Connects>` + shell.slice(root2.closeStart);
705
- return shell.replace(SHAPES_MARK, () => page.shapes.map((s) => s.xml).join("")).replace(CONNECTS_MARK, () => page.connects.map(connectXml).join(""));
706
- }
707
- function relTarget(pagesDir, path) {
708
- return pagesDir && path.startsWith(`${pagesDir}/`) ? path.slice(pagesDir.length + 1) : `/${path}`;
1059
+ function contentsXml(shell, root, shapesXml, hasShapes, connects) {
1060
+ const outer = scanContainer(shell, root);
1061
+ if (hasShapes && !shell.includes(SHAPES_MARK) && outer && !outer.selfClosing)
1062
+ shell = shell.slice(0, outer.openEnd) + `<Shapes>${SHAPES_MARK}</Shapes>` + shell.slice(outer.openEnd);
1063
+ const outer2 = scanContainer(shell, root);
1064
+ if (connects.length && !shell.includes(CONNECTS_MARK) && outer2 && !outer2.selfClosing)
1065
+ shell = shell.slice(0, outer2.closeStart) + `<Connects>${CONNECTS_MARK}</Connects>` + shell.slice(outer2.closeStart);
1066
+ return shell.replace(SHAPES_MARK, () => shapesXml).replace(CONNECTS_MARK, () => connects.map(connectXml).join(""));
709
1067
  }
1068
+ var pagePartXml = (page) => contentsXml(
1069
+ page.body.shell,
1070
+ "PageContents",
1071
+ page.shapesXml,
1072
+ page.shapes.length > 0,
1073
+ page.connects
1074
+ );
1075
+ var masterPartXml = (master) => contentsXml(
1076
+ decoder.decode(fromBase64(master.part)),
1077
+ "MasterContents",
1078
+ master.shapesXml,
1079
+ master.shapes.length > 0,
1080
+ master.connects
1081
+ );
710
1082
  async function composeDrawing(drawing, local, keys, options = {}) {
711
1083
  const localZip = local ? await readZip(local) : void 0;
712
1084
  const files = /* @__PURE__ */ new Map();
713
1085
  const prints = /* @__PURE__ */ new Map();
714
1086
  let total = 0;
715
1087
  const limit = options.maxPartBytes ?? 48 * 1024 * 1024;
716
- for (const [path, b64] of drawing.parts) {
717
- const data = fromBase64(b64);
1088
+ const cache = keys.cache.parts;
1089
+ const decode = (path, b64) => {
1090
+ const hit = cache.get(path);
1091
+ const data = hit && hit.b64 === b64 ? hit.data : fromBase64(b64);
1092
+ if (!hit || hit.b64 !== b64) cache.set(path, { b64, data });
718
1093
  total += data.length;
719
1094
  if (total > limit) throw new Error("The shared parts exceed the size limit.");
1095
+ return data;
1096
+ };
1097
+ for (const [path, b64] of drawing.parts) {
1098
+ const data = decode(path, b64);
720
1099
  files.set(path, data);
721
1100
  prints.set(path, fingerprint(data));
722
1101
  }
1102
+ for (const path of [...cache.keys()])
1103
+ if (!drawing.parts.has(path) && !path.startsWith("media:")) cache.delete(path);
723
1104
  if (localZip)
724
1105
  for (const path of Object.keys(localZip.files)) {
725
1106
  const file = localZip.files[path];
@@ -727,6 +1108,10 @@ async function composeDrawing(drawing, local, keys, options = {}) {
727
1108
  files.set(path, await file.async("uint8array"));
728
1109
  }
729
1110
  let contentTypes = files.get(CONTENT_TYPES) !== void 0 ? decoder.decode(files.get(CONTENT_TYPES)) : (localZip && await zipText(localZip, CONTENT_TYPES)) ?? DEFAULT_CONTENT_TYPES;
1111
+ for (const item of drawing.media) {
1112
+ files.set(item.path, decode(`media:${item.path}`, item.data));
1113
+ contentTypes = ensureType(contentTypes, item.path, item.declared);
1114
+ }
730
1115
  for (const [path, data] of [...files]) {
731
1116
  if (!path.endsWith(".rels") || typeof data === "string") continue;
732
1117
  const source = path.replace(/(^|\/)_rels\/([^/]+)\.rels$/, "$1$2");
@@ -745,65 +1130,56 @@ async function composeDrawing(drawing, local, keys, options = {}) {
745
1130
  });
746
1131
  if (next !== text) files.set(path, encoder.encode(next));
747
1132
  }
748
- const pagesDir = dirName(drawing.pagesPath);
749
- const taken = /* @__PURE__ */ new Set([
750
- ...files.keys(),
751
- drawing.pagesPath,
752
- relsPathOf(drawing.pagesPath)
753
- ]);
754
- const paths = /* @__PURE__ */ new Map();
755
- for (const page of drawing.pages) {
756
- const known = keys.pagePaths.get(page.key);
757
- if (known && !taken.has(known) && localZip?.file(known)) {
758
- paths.set(page.key, known);
759
- taken.add(known);
760
- }
761
- }
762
- let counter = 0;
763
- for (const page of drawing.pages) {
764
- if (paths.has(page.key)) continue;
765
- let path;
766
- do
767
- path = `${pagesDir ? `${pagesDir}/` : ""}page${++counter}.xml`;
768
- while (taken.has(path) || localZip?.file(path));
769
- paths.set(page.key, path);
770
- taken.add(path);
771
- }
772
- const others = parseRelationships(
773
- localZip ? await zipText(localZip, relsPathOf(drawing.pagesPath)) : void 0
774
- ).filter((r) => r.type !== PAGE_TYPE);
775
- const usedIds = new Set(others.map((r) => r.id));
776
- let nextRel = 0;
777
- const pageRels = [];
778
- const pageXml = [];
779
- for (const page of drawing.pages) {
780
- const path = paths.get(page.key);
781
- let id;
782
- do
783
- id = `rId${++nextRel}`;
784
- while (usedIds.has(id));
785
- usedIds.add(id);
786
- pageRels.push(
787
- `<Relationship Id="${id}" Type="${PAGE_TYPE}" Target="${escapeAttr(relTarget(pagesDir, path))}"/>`
788
- );
789
- const rel = `<Rel xmlns:r="${REL_NS}" r:id="${id}"/>`;
790
- pageXml.push(
791
- page.xml.includes(REL_MARK) ? page.xml.replace(REL_MARK, () => rel) : page.xml.slice(0, page.xml.lastIndexOf("</")) + rel + page.xml.slice(page.xml.lastIndexOf("</"))
792
- );
793
- files.set(path, encoder.encode(pagePartXml(page)));
794
- if (page.body.rels !== void 0) files.set(relsPathOf(path), encoder.encode(page.body.rels));
1133
+ const taken = new Set(files.keys());
1134
+ const pageCache = keys.cache.pageParts;
1135
+ const pageItems = drawing.pages.map((page) => {
1136
+ const hit = pageCache.get(page.key);
1137
+ const bytes2 = hit && hit.sig === page.signature ? hit.bytes : encoder.encode(pagePartXml(page));
1138
+ pageCache.set(page.key, { sig: page.signature, bytes: bytes2 });
1139
+ return {
1140
+ key: page.key,
1141
+ xml: page.xml,
1142
+ part: bytes2,
1143
+ ...page.body.rels !== void 0 ? { rels: page.body.rels } : {}
1144
+ };
1145
+ });
1146
+ for (const key of [...pageCache.keys()])
1147
+ if (!drawing.pages.some((p) => p.key === key)) pageCache.delete(key);
1148
+ const pagePaths = await placeList({
1149
+ listPath: drawing.pagesPath,
1150
+ open: drawing.pagesOpen,
1151
+ close: drawing.pagesClose,
1152
+ relType: PAGE_TYPE,
1153
+ stem: "page",
1154
+ items: pageItems,
1155
+ known: keys.pagePaths,
1156
+ files,
1157
+ taken,
1158
+ localZip
1159
+ });
1160
+ contentTypes = withPageOverrides(contentTypes, [...pagePaths.values()]);
1161
+ let masterPaths = /* @__PURE__ */ new Map();
1162
+ if (drawing.mastersPath !== void 0) {
1163
+ masterPaths = await placeList({
1164
+ listPath: drawing.mastersPath,
1165
+ open: drawing.mastersOpen ?? "<Masters>",
1166
+ close: drawing.mastersClose ?? "</Masters>",
1167
+ relType: MASTER_TYPE,
1168
+ stem: "master",
1169
+ items: drawing.masters.map((m) => ({
1170
+ key: m.key,
1171
+ xml: m.xml,
1172
+ part: encoder.encode(masterPartXml(m)),
1173
+ ...m.rels !== void 0 ? { rels: m.rels } : {}
1174
+ })),
1175
+ known: keys.masterPaths,
1176
+ files,
1177
+ taken,
1178
+ localZip
1179
+ });
1180
+ contentTypes = withOverrides(contentTypes, MASTER_CONTENT_TYPE, [...masterPaths.values()]);
795
1181
  }
796
- files.set(
797
- drawing.pagesPath,
798
- encoder.encode(drawing.pagesOpen + pageXml.join("") + drawing.pagesClose)
799
- );
800
- files.set(
801
- relsPathOf(drawing.pagesPath),
802
- encoder.encode(
803
- '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' + others.map((r) => r.text).join("") + pageRels.join("") + "</Relationships>"
804
- )
805
- );
806
- files.set(CONTENT_TYPES, encoder.encode(withPageOverrides(contentTypes, [...paths.values()])));
1182
+ files.set(CONTENT_TYPES, encoder.encode(contentTypes));
807
1183
  const zip = new JSZip__default.default();
808
1184
  const names = [...files.keys()].sort(
809
1185
  (a, b) => a === CONTENT_TYPES ? -1 : b === CONTENT_TYPES ? 1 : 0
@@ -812,24 +1188,58 @@ async function composeDrawing(drawing, local, keys, options = {}) {
812
1188
  const bytes = await zip.generateAsync({ type: "uint8array", compression: "STORE" });
813
1189
  const commit = () => {
814
1190
  const pageIds = /* @__PURE__ */ new Map();
815
- const shapeIds2 = /* @__PURE__ */ new Map();
1191
+ const shapeIds = /* @__PURE__ */ new Map();
1192
+ const masterIds = /* @__PURE__ */ new Map();
816
1193
  const known = /* @__PURE__ */ new Map();
1194
+ const recordShapes = (scope, shapes, connects) => {
1195
+ shapeIds.set(scope, new Map(shapes.map((s) => [s.id, s.key])));
1196
+ for (const shape of shapes) {
1197
+ known.set(shapeId(scope, shape.key), shellContent(shape));
1198
+ for (const [path, entry] of shape.entries)
1199
+ known.set(cellId(scope, shape.key, path), entryContent(entry));
1200
+ }
1201
+ for (const connect of connects) known.set(connectId(connect.key), "");
1202
+ };
817
1203
  keys.pagePaths.clear();
1204
+ keys.masterPaths.clear();
1205
+ keys.mediaKeys.clear();
818
1206
  for (const page of drawing.pages) {
819
1207
  pageIds.set(page.id, page.key);
820
- keys.pagePaths.set(page.key, paths.get(page.key));
1208
+ keys.pagePaths.set(page.key, pagePaths.get(page.key));
821
1209
  known.set(pageId(page.key), pageContent(page));
822
1210
  known.set(bodyId(page.key), bodyContent(page.body));
823
- shapeIds2.set(page.key, new Map(page.shapes.map((s) => [s.id, s.key])));
824
- for (const shape of page.shapes) known.set(shapeId(page.key, shape.key), canon(shape.xml));
825
- for (const connect of page.connects) known.set(connectId(connect.key), "");
1211
+ recordShapes(page.key, page.shapes, page.connects);
1212
+ }
1213
+ for (const master of drawing.masters) {
1214
+ masterIds.set(master.id, master.key);
1215
+ recordShapes(masterScope(master.key), master.shapes, master.connects);
1216
+ const path = masterPaths.get(master.key);
1217
+ if (path) keys.masterPaths.set(master.key, path);
1218
+ known.set(
1219
+ masterId(master.key),
1220
+ masterContent({ ...master, print: fingerprint(fromBase64(master.part)) })
1221
+ );
826
1222
  }
827
- keys.reset(pageIds, shapeIds2);
1223
+ for (const item of drawing.media) {
1224
+ known.set(mediaId(item.key), "");
1225
+ keys.mediaKeys.set(item.path, item.key);
1226
+ }
1227
+ keys.reset(pageIds, shapeIds, masterIds);
828
1228
  keys.known = known;
829
1229
  keys.partPrints.clear();
830
1230
  for (const [path, print] of prints) keys.partPrints.set(path, print);
831
1231
  };
832
- return { bytes, commit };
1232
+ return { bytes, signature: drawing.signature, commit };
1233
+ }
1234
+
1235
+ // visio/collab/fixups.ts
1236
+ function applyFixups(shared, fixups) {
1237
+ let changed = 0;
1238
+ for (const fixup of fixups) {
1239
+ const map = fixup.map === "renum" ? shared.renum : shared.cells;
1240
+ if (setIfChanged(map, fixup.key, fixup.value)) changed++;
1241
+ }
1242
+ return changed;
833
1243
  }
834
1244
 
835
1245
  // visio/collab/keys.ts
@@ -843,15 +1253,34 @@ var DrawingKeys = class {
843
1253
  seeding = false;
844
1254
  #pages = /* @__PURE__ */ new Map();
845
1255
  #shapes = /* @__PURE__ */ new Map();
1256
+ #masters = /* @__PURE__ */ new Map();
846
1257
  /** Content of each entry this peer last wrote or applied, to tell a local edit or deletion from a change it has not applied yet. */
847
1258
  known = /* @__PURE__ */ new Map();
848
1259
  /** Fingerprints of the parts this peer last wrote or applied. */
849
1260
  partPrints = /* @__PURE__ */ new Map();
850
1261
  /** The part path of each page key in the local package. */
851
1262
  pagePaths = /* @__PURE__ */ new Map();
1263
+ /** The part path of each master key in the local package. */
1264
+ masterPaths = /* @__PURE__ */ new Map();
1265
+ /** The room key of each media path in the local package. */
1266
+ mediaKeys = /* @__PURE__ */ new Map();
1267
+ /** Serialised page parts and decoded shared parts of the last rebuild, to skip unchanged work. */
1268
+ cache = {
1269
+ pageParts: /* @__PURE__ */ new Map(),
1270
+ parts: /* @__PURE__ */ new Map()
1271
+ };
1272
+ /**
1273
+ * What was last applied to the editor: the drawing signature, the editor version it produced,
1274
+ * the signature of everything but page content and the content signature of each page.
1275
+ */
1276
+ applied;
852
1277
  mint() {
853
1278
  return `${this.tag}.${(++this.#counter).toString(36)}`;
854
1279
  }
1280
+ /** A fresh id for one write batch: entries written together share it. */
1281
+ batch() {
1282
+ return this.mint();
1283
+ }
855
1284
  pageKey(id) {
856
1285
  let key = this.#pages.get(id);
857
1286
  if (key === void 0) {
@@ -870,6 +1299,14 @@ var DrawingKeys = class {
870
1299
  }
871
1300
  return key;
872
1301
  }
1302
+ masterKey(id) {
1303
+ let key = this.#masters.get(id);
1304
+ if (key === void 0) {
1305
+ key = this.seeding ? `s${id}` : this.mint();
1306
+ this.#masters.set(id, key);
1307
+ }
1308
+ return key;
1309
+ }
873
1310
  findPage(id) {
874
1311
  return this.#pages.get(id);
875
1312
  }
@@ -883,38 +1320,355 @@ var DrawingKeys = class {
883
1320
  for (const [id, k] of map) if (k === key) map.delete(id);
884
1321
  }
885
1322
  }
1323
+ forgetMaster(key) {
1324
+ for (const [id, k] of this.#masters) if (k === key) this.#masters.delete(id);
1325
+ this.#shapes.delete(masterScope(key));
1326
+ this.masterPaths.delete(key);
1327
+ }
886
1328
  /** Replaces the id-to-key tables with those of a freshly composed package. */
887
- reset(pages, shapes) {
1329
+ reset(pages, shapes, masters = /* @__PURE__ */ new Map()) {
888
1330
  this.#pages.clear();
889
1331
  this.#shapes.clear();
1332
+ this.#masters.clear();
890
1333
  for (const [id, key] of pages) this.#pages.set(id, key);
891
1334
  for (const [page, map] of shapes) this.#shapes.set(page, new Map(map));
1335
+ for (const [id, key] of masters) this.#masters.set(id, key);
892
1336
  }
893
1337
  };
894
1338
 
895
- // visio/collab/read.ts
1339
+ // visio/collab/order.ts
1340
+ function assignPositions(order, current) {
1341
+ const result = /* @__PURE__ */ new Map();
1342
+ const kept = [];
1343
+ let last = -Infinity;
1344
+ for (const key of order) {
1345
+ const pos = current(key);
1346
+ if (pos !== void 0 && pos > last) {
1347
+ kept.push(pos);
1348
+ last = pos;
1349
+ } else kept.push(void 0);
1350
+ }
1351
+ let i = 0;
1352
+ while (i < order.length) {
1353
+ const known = kept[i];
1354
+ if (known !== void 0) {
1355
+ result.set(order[i], known);
1356
+ i++;
1357
+ continue;
1358
+ }
1359
+ let end = i;
1360
+ while (end < order.length && kept[end] === void 0) end++;
1361
+ const lo = i > 0 ? kept[i - 1] : void 0;
1362
+ const hi = end < order.length ? kept[end] : void 0;
1363
+ const count = end - i;
1364
+ for (let j = 0; j < count; j++) {
1365
+ const key = order[i + j];
1366
+ if (lo !== void 0 && hi !== void 0)
1367
+ result.set(key, lo + (hi - lo) * (j + 1) / (count + 1));
1368
+ else if (lo !== void 0) result.set(key, lo + j + 1);
1369
+ else if (hi !== void 0) result.set(key, hi - count + j);
1370
+ else result.set(key, j + 1);
1371
+ }
1372
+ i = end;
1373
+ }
1374
+ return result;
1375
+ }
1376
+ function byPosition(items) {
1377
+ return items.sort((a, b) => a.pos - b.pos || (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
1378
+ }
1379
+
1380
+ // visio/collab/read-ids.ts
896
1381
  var compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
897
1382
  var numeric = (id) => /^\d+$/.test(id) ? Number(id) : 0;
1383
+ function assignIds(items) {
1384
+ const effective = (item) => item.fixed ?? item.id;
1385
+ let max = Math.max(0, ...items.map((item) => numeric(effective(item))));
1386
+ const order = [...items].sort(
1387
+ (a, b) => Number(b.fixed !== void 0) - Number(a.fixed !== void 0) || compare(a.key, b.key)
1388
+ );
1389
+ const used = /* @__PURE__ */ new Set();
1390
+ const ids = /* @__PURE__ */ new Map();
1391
+ const fresh = /* @__PURE__ */ new Map();
1392
+ for (const item of order) {
1393
+ let id = effective(item);
1394
+ if (used.has(id)) {
1395
+ id = String(++max);
1396
+ fresh.set(item.key, { from: item.id, to: id });
1397
+ }
1398
+ used.add(id);
1399
+ ids.set(item.key, id);
1400
+ }
1401
+ return { ids, fresh };
1402
+ }
1403
+ var fixedId = (renum, key, id) => {
1404
+ const pair = renum.get(key)?.i;
1405
+ return pair && pair[0] === id ? pair[1] : void 0;
1406
+ };
1407
+ function readRenum(shared) {
1408
+ const out = /* @__PURE__ */ new Map();
1409
+ for (const [key, value] of shared.renum) {
1410
+ const entry = renumEntry(value);
1411
+ if (entry) out.set(key, entry);
1412
+ }
1413
+ return out;
1414
+ }
1415
+
1416
+ // visio/collab/read-util.ts
1417
+ function hashTexts(texts) {
1418
+ let a = 2166136261;
1419
+ let b = 16777619 ^ 2654435769;
1420
+ for (const text of texts) {
1421
+ for (let i = 0; i < text.length; i++) {
1422
+ const c = text.charCodeAt(i);
1423
+ a = Math.imul(a ^ c, 16777619);
1424
+ b = Math.imul(b + c, 2246822507) ^ b >>> 13;
1425
+ }
1426
+ a = Math.imul(a ^ 255, 16777619);
1427
+ b = Math.imul(b ^ 255, 3266489909);
1428
+ }
1429
+ return `${(a >>> 0).toString(16)}${(b >>> 0).toString(16)}`;
1430
+ }
1431
+ var split = (key, depth) => {
1432
+ const out = [];
1433
+ let from = 0;
1434
+ for (let i = 0; i < depth; i++) {
1435
+ const slash = key.indexOf("/", from);
1436
+ if (slash < 0) return void 0;
1437
+ out.push(key.slice(from, slash));
1438
+ from = slash + 1;
1439
+ }
1440
+ out.push(key.slice(from));
1441
+ return out;
1442
+ };
1443
+ var connectSignature = (connects) => connects.flatMap((c) => [c.from, c.to, c.entry.fc, c.entry.tc, c.entry.fp, c.entry.tp]);
1444
+ function resolveConnects(list, shapes) {
1445
+ const byKey = new Map(shapes.map((s) => [s.key, s]));
1446
+ const out = [];
1447
+ for (const item of [...list ?? []].sort((a, b) => compare(a.key, b.key))) {
1448
+ const { entry } = item;
1449
+ const from = entry.fk ? byKey.get(entry.fk) : void 0;
1450
+ const to = entry.tk ? byKey.get(entry.tk) : void 0;
1451
+ if (entry.fk && !from || entry.tk && !to) continue;
1452
+ out.push({ key: item.key, entry, from: from?.id ?? entry.fi, to: to?.id ?? entry.ti });
1453
+ }
1454
+ return out;
1455
+ }
1456
+
1457
+ // visio/collab/read-shapes.ts
898
1458
  function setAttr(open, name, value) {
899
1459
  return open.replace(
900
1460
  new RegExp(String.raw`(\s${name}\s*=\s*)(?:"[^"]*"|'[^']*')`),
901
1461
  (_m, head) => `${head}"${value}"`
902
1462
  );
903
1463
  }
904
- function renumberShape(xml, remap) {
905
- if (!remap.size) return xml;
906
- return xml.replace(
907
- /<(?:\w+:)?Shape\s[^>]*>/g,
908
- (tag) => tag.replace(/(\bID\s*=\s*)(["'])(\d+)\2/, (m, head, q, id) => {
1464
+ function renumberFormulas(xml, remap) {
1465
+ if (!remap.size || !xml.includes("Sheet.")) return xml;
1466
+ return xml.replace(/(\sF\s*=\s*)("[^"]*"|'[^']*')/g, (_m, head, value) => {
1467
+ const body = value.slice(1, -1).replace(/Sheet\.(\d+)(?!\d)/g, (m, id) => {
909
1468
  const next = remap.get(id);
910
- return next === void 0 ? m : `${head}${q}${next}${q}`;
1469
+ return next === void 0 ? m : `Sheet.${next}`;
1470
+ });
1471
+ return `${head}${value[0]}${body}${value[0]}`;
1472
+ });
1473
+ }
1474
+ function resolveShapes(input) {
1475
+ const raw = /* @__PURE__ */ new Map();
1476
+ for (const [key, entry] of input.shapes) {
1477
+ const id = attrOf(entry.open, "ID");
1478
+ if (id !== void 0) raw.set(key, { entry, id });
1479
+ }
1480
+ const state = /* @__PURE__ */ new Map();
1481
+ const valid = (key, seen = /* @__PURE__ */ new Set()) => {
1482
+ const known = state.get(key);
1483
+ if (known !== void 0) return known;
1484
+ const item = raw.get(key);
1485
+ const parent = item?.entry.parent;
1486
+ let ok = !!item;
1487
+ if (ok && parent !== void 0) ok = !seen.has(key) && valid(parent, seen.add(key));
1488
+ state.set(key, ok);
1489
+ return ok;
1490
+ };
1491
+ const live = [...raw.keys()].filter((k) => valid(k)).sort(compare);
1492
+ const { ids: finalId, fresh: renumbered } = assignIds(
1493
+ live.map((key) => {
1494
+ const { id } = raw.get(key);
1495
+ return { key, id, fixed: fixedId(input.renum, key, id) };
911
1496
  })
912
- ).replace(/Sheet\.(\d+)(?!\d)/g, (m, id) => {
913
- const next = remap.get(id);
914
- return next === void 0 ? m : `Sheet.${next}`;
1497
+ );
1498
+ const batchRemap = /* @__PURE__ */ new Map();
1499
+ for (const [key, change] of renumbered) {
1500
+ const b = raw.get(key).entry.b;
1501
+ if (b === void 0) continue;
1502
+ const map = batchRemap.get(b) ?? /* @__PURE__ */ new Map();
1503
+ map.set(change.from, change.to);
1504
+ batchRemap.set(b, map);
1505
+ }
1506
+ const remapFor = (key) => {
1507
+ const out = new Map(batchRemap.get(raw.get(key).entry.b ?? "") ?? []);
1508
+ const chain = [];
1509
+ for (let k = key; k !== void 0; k = raw.get(k)?.entry.parent)
1510
+ chain.unshift(k);
1511
+ for (const k of chain) {
1512
+ const change = renumbered.get(k);
1513
+ if (change) out.set(change.from, change.to);
1514
+ }
1515
+ return out;
1516
+ };
1517
+ const children = /* @__PURE__ */ new Map();
1518
+ for (const key of live) {
1519
+ const parent = raw.get(key).entry.parent ?? "";
1520
+ children.set(parent, [...children.get(parent) ?? [], key]);
1521
+ }
1522
+ const sortSiblings = (keys) => keys.sort((a, b) => raw.get(a).entry.pos - raw.get(b).entry.pos || compare(a, b));
1523
+ const shapes = [];
1524
+ const fixups = [];
1525
+ const render = (key) => {
1526
+ const { entry } = raw.get(key);
1527
+ const id = finalId.get(key);
1528
+ const remap = remapFor(key);
1529
+ let open = setAttr(entry.open, "ID", id);
1530
+ const record = { ...input.renum.get(key) };
1531
+ let dirty = false;
1532
+ const change = renumbered.get(key);
1533
+ if (change) {
1534
+ record.i = [change.from, change.to];
1535
+ dirty = true;
1536
+ }
1537
+ const master = attrOf(open, "Master");
1538
+ const persisted = record.m && record.m[0] === master ? record.m[1] : void 0;
1539
+ const batched = master !== void 0 ? input.masterRemap.get(entry.b ?? "")?.get(master) : void 0;
1540
+ const next = persisted ?? batched;
1541
+ if (next !== void 0) open = setAttr(open, "Master", next);
1542
+ if (persisted === void 0 && batched !== void 0) {
1543
+ record.m = [master, batched];
1544
+ dirty = true;
1545
+ }
1546
+ if (dirty) fixups.push({ map: "renum", key: `${input.scope}/${key}`, value: record });
1547
+ const entries = /* @__PURE__ */ new Map();
1548
+ for (const [path, piece] of input.cells.get(key) ?? []) {
1549
+ const leaf = piece.xml !== void 0 && /(^|\/)c\/[^/]*$/.test(path);
1550
+ const xml2 = leaf ? renumberFormulas(piece.xml, remap) : void 0;
1551
+ if (xml2 !== void 0 && xml2 !== piece.xml) {
1552
+ const value = { ...piece, xml: xml2 };
1553
+ entries.set(path, value);
1554
+ fixups.push({ map: "cells", key: `${input.scope}/${key}/${path}`, value });
1555
+ } else entries.set(path, piece);
1556
+ }
1557
+ shapes.push({
1558
+ key,
1559
+ id,
1560
+ ...entry.parent !== void 0 ? { parent: entry.parent } : {},
1561
+ open,
1562
+ entries
1563
+ });
1564
+ const inner = sortSiblings(children.get(key) ?? []).map(render).join("");
1565
+ return buildShape(open, entries, inner ? `<Shapes>${inner}</Shapes>` : "");
1566
+ };
1567
+ const xml = sortSiblings(children.get("") ?? []).map(render).join("");
1568
+ return { shapes, xml, fixups };
1569
+ }
1570
+
1571
+ // visio/collab/read-parts.ts
1572
+ var compare2 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
1573
+ function resolveMasters(shared, renum) {
1574
+ const alive = [];
1575
+ for (const [key, value] of shared.masters) {
1576
+ const entry = masterEntry(value);
1577
+ if (entry) alive.push({ key, entry });
1578
+ }
1579
+ const { ids, fresh } = assignIds(
1580
+ alive.map(({ key, entry }) => ({
1581
+ key,
1582
+ id: entry.id,
1583
+ fixed: fixedId(renum, masterScope(key), entry.id)
1584
+ }))
1585
+ );
1586
+ const remap = /* @__PURE__ */ new Map();
1587
+ const fixups = [];
1588
+ for (const [key, change] of [...fresh].sort((a, b) => compare2(a[0], b[0]))) {
1589
+ const b = alive.find((m) => m.key === key).entry.b;
1590
+ if (b !== void 0) {
1591
+ const map = remap.get(b) ?? /* @__PURE__ */ new Map();
1592
+ map.set(change.from, change.to);
1593
+ remap.set(b, map);
1594
+ }
1595
+ fixups.push({
1596
+ map: "renum",
1597
+ key: masterScope(key),
1598
+ value: { ...renum.get(masterScope(key)), i: [change.from, change.to] }
1599
+ });
1600
+ }
1601
+ alive.sort((a, b) => a.entry.pos - b.entry.pos || compare2(a.key, b.key));
1602
+ return {
1603
+ masters: alive.map(({ key, entry }) => {
1604
+ const id = ids.get(key);
1605
+ const open = /^<[^>]*>/.exec(entry.xml)?.[0];
1606
+ return {
1607
+ key,
1608
+ id,
1609
+ xml: open ? setAttr(open, "ID", id) + entry.xml.slice(open.length) : entry.xml,
1610
+ part: entry.part,
1611
+ ...entry.rels !== void 0 ? { rels: entry.rels } : {}
1612
+ };
1613
+ }),
1614
+ remap,
1615
+ fixups
1616
+ };
1617
+ }
1618
+ function resolveMedia(shared) {
1619
+ const items = [...shared.media].flatMap(([key, value]) => {
1620
+ const entry = mediaEntry(value);
1621
+ const data = entry ? str(shared.blobs.get(entry.hash)) : void 0;
1622
+ return entry && data !== void 0 ? [{ key, entry, data }] : [];
1623
+ }).sort((a, b) => compare2(a.key, b.key));
1624
+ const taken = /* @__PURE__ */ new Set();
1625
+ const first = /* @__PURE__ */ new Map();
1626
+ for (const item of items) if (!first.has(item.entry.path)) first.set(item.entry.path, item.key);
1627
+ for (const path of first.keys()) taken.add(path);
1628
+ const renames = /* @__PURE__ */ new Map();
1629
+ const media = [];
1630
+ for (const { key, entry, data } of items) {
1631
+ let path = entry.path;
1632
+ if (first.get(entry.path) !== key) {
1633
+ const dot = path.lastIndexOf(".");
1634
+ const stem = dot > path.lastIndexOf("/") ? path.slice(0, dot) : path;
1635
+ const ext = dot > path.lastIndexOf("/") ? path.slice(dot) : "";
1636
+ path = `${stem}-${entry.hash.slice(0, 8)}${ext}`;
1637
+ for (let n = 2; taken.has(path); n++) path = `${stem}-${entry.hash.slice(0, 8)}-${n}${ext}`;
1638
+ taken.add(path);
1639
+ if (entry.b !== void 0) {
1640
+ const map = renames.get(entry.b) ?? /* @__PURE__ */ new Map();
1641
+ map.set(entry.path, path);
1642
+ renames.set(entry.b, map);
1643
+ }
1644
+ }
1645
+ media.push({
1646
+ key,
1647
+ path,
1648
+ hash: entry.hash,
1649
+ declared: { type: entry.ct, byDefault: entry.d },
1650
+ data
1651
+ });
1652
+ }
1653
+ return { media, renames };
1654
+ }
1655
+ function renameTargets(rels, baseDir, renames) {
1656
+ return rels.replace(/<(?:\w+:)?Relationship\b[^>]*>/g, (tag) => {
1657
+ const target = attrOf(tag, "Target");
1658
+ if (target === void 0 || attrOf(tag, "TargetMode") === "External") return tag;
1659
+ const next = renames.get(resolveTarget(baseDir, target));
1660
+ if (next === void 0) return tag;
1661
+ const value = target.startsWith("/") ? `/${next}` : relativeTarget(baseDir, next);
1662
+ return tag.replace(
1663
+ /(\sTarget\s*=\s*)(?:"[^"]*"|'[^']*')/,
1664
+ (_m, head) => `${head}"${value}"`
1665
+ );
915
1666
  });
916
1667
  }
1668
+
1669
+ // visio/collab/read.ts
917
1670
  function readSharedDrawing(shared) {
1671
+ assertSchemaVersion(shared);
918
1672
  if (isSharedDrawingEmpty(shared)) return void 0;
919
1673
  const pagesPath = str(shared.meta.get("pagesPath"));
920
1674
  const pagesOpen = str(shared.meta.get("pagesOpen"));
@@ -926,25 +1680,50 @@ function readSharedDrawing(shared) {
926
1680
  const body = bodyEntry(shared.bodies.get(key));
927
1681
  return entry && body ? [{ key, pos: entry.pos, entry, body }] : [];
928
1682
  });
929
- const pageIds = /* @__PURE__ */ new Map();
930
- const taken = /* @__PURE__ */ new Set();
931
- let maxPage = Math.max(0, ...alive.map((p) => numeric(p.entry.id)));
932
- for (const page of [...alive].sort((a, b) => compare(a.key, b.key))) {
933
- let id = page.entry.id;
934
- if (taken.has(id)) id = String(++maxPage);
935
- taken.add(id);
936
- pageIds.set(page.key, id);
937
- }
1683
+ const renum = readRenum(shared);
1684
+ const fixups = [];
1685
+ const { ids: pageIds, fresh: freshPages } = assignIds(
1686
+ alive.map((p) => ({
1687
+ key: p.key,
1688
+ id: p.entry.id,
1689
+ fixed: fixedId(renum, p.key, p.entry.id)
1690
+ }))
1691
+ );
1692
+ for (const [key, change] of [...freshPages].sort((a, b) => compare(a[0], b[0])))
1693
+ fixups.push({
1694
+ map: "renum",
1695
+ key,
1696
+ value: { ...renum.get(key), i: [change.from, change.to] }
1697
+ });
938
1698
  const shapesByPage = /* @__PURE__ */ new Map();
939
1699
  for (const [full, value] of shared.shapes) {
940
- const slash = full.indexOf("/");
941
- if (slash < 0 || shared.dead.has(full)) continue;
1700
+ const parts2 = split(full, 1);
1701
+ if (!parts2 || shared.dead.has(full)) continue;
942
1702
  const entry = shapeEntry(value);
943
1703
  if (!entry) continue;
944
- const page = full.slice(0, slash);
945
- const list = shapesByPage.get(page) ?? [];
946
- list.push({ key: full.slice(slash + 1), pos: entry.pos, xml: entry.xml });
947
- shapesByPage.set(page, list);
1704
+ const map = shapesByPage.get(parts2[0]) ?? /* @__PURE__ */ new Map();
1705
+ map.set(parts2[1], entry);
1706
+ shapesByPage.set(parts2[0], map);
1707
+ }
1708
+ const cellsByPage = /* @__PURE__ */ new Map();
1709
+ for (const [full, value] of shared.cells) {
1710
+ const parts2 = split(full, 2);
1711
+ const entry = parts2 && cellEntry(value);
1712
+ if (!parts2 || !entry) continue;
1713
+ const [page, shape, path] = parts2;
1714
+ const shapes = cellsByPage.get(page) ?? /* @__PURE__ */ new Map();
1715
+ const cells = shapes.get(shape) ?? /* @__PURE__ */ new Map();
1716
+ cells.set(path, entry);
1717
+ shapes.set(shape, cells);
1718
+ cellsByPage.set(page, shapes);
1719
+ }
1720
+ const renumByScope = /* @__PURE__ */ new Map();
1721
+ for (const [full, entry] of renum) {
1722
+ const parts2 = split(full, 1);
1723
+ if (!parts2) continue;
1724
+ const map = renumByScope.get(parts2[0]) ?? /* @__PURE__ */ new Map();
1725
+ map.set(parts2[1], entry);
1726
+ renumByScope.set(parts2[0], map);
948
1727
  }
949
1728
  const connectsByPage = /* @__PURE__ */ new Map();
950
1729
  for (const [full, value] of shared.connects) {
@@ -955,24 +1734,25 @@ function readSharedDrawing(shared) {
955
1734
  list.push({ key: full, entry });
956
1735
  connectsByPage.set(full.slice(0, slash), list);
957
1736
  }
1737
+ const { masters, remap: masterRemap, fixups: masterFixups } = resolveMasters(shared, renum);
1738
+ fixups.push(...masterFixups);
1739
+ const { media, renames } = resolveMedia(shared);
1740
+ const mastersPath = str(shared.meta.get("mastersPath"));
1741
+ const mastersOpen = str(shared.meta.get("mastersOpen"));
1742
+ const mastersClose = str(shared.meta.get("mastersClose"));
1743
+ const pagesDir = dirName(pagesPath);
958
1744
  const pages = [];
959
1745
  for (const page of byPosition(alive)) {
960
1746
  const finalId = pageIds.get(page.key);
961
- const shapes = resolveShapes(shapesByPage.get(page.key) ?? []);
962
- const byKey = new Map(shapes.map((s) => [s.key, s]));
963
- const connects = [];
964
- for (const item of (connectsByPage.get(page.key) ?? []).sort((a, b) => compare(a.key, b.key))) {
965
- const { entry } = item;
966
- const from = entry.fk ? byKey.get(entry.fk) : void 0;
967
- const to = entry.tk ? byKey.get(entry.tk) : void 0;
968
- if (entry.fk && !from || entry.tk && !to) continue;
969
- connects.push({
970
- key: item.key,
971
- entry,
972
- from: from?.remap.get(entry.fi) ?? entry.fi,
973
- to: to?.remap.get(entry.ti) ?? entry.ti
974
- });
975
- }
1747
+ const resolved = resolveShapes({
1748
+ scope: page.key,
1749
+ shapes: shapesByPage.get(page.key) ?? /* @__PURE__ */ new Map(),
1750
+ cells: cellsByPage.get(page.key) ?? /* @__PURE__ */ new Map(),
1751
+ renum: renumByScope.get(page.key) ?? /* @__PURE__ */ new Map(),
1752
+ masterRemap
1753
+ });
1754
+ fixups.push(...resolved.fixups);
1755
+ const connects = resolveConnects(connectsByPage.get(page.key), resolved.shapes);
976
1756
  let xml = page.entry.xml;
977
1757
  const open = /^<[^>]*>/.exec(xml)?.[0];
978
1758
  if (open) {
@@ -981,46 +1761,360 @@ function readSharedDrawing(shared) {
981
1761
  if (back !== void 0) next = setAttr(next, "BackPage", back);
982
1762
  xml = next + xml.slice(open.length);
983
1763
  }
1764
+ const rename = page.body.b !== void 0 ? renames.get(page.body.b) : void 0;
1765
+ const body = rename && page.body.rels !== void 0 ? { ...page.body, rels: renameTargets(page.body.rels, pagesDir, rename) } : page.body;
1766
+ const contentSignature = hashTexts([
1767
+ body.shell,
1768
+ body.rels ?? "\0",
1769
+ resolved.xml,
1770
+ ...connectSignature(connects)
1771
+ ]);
984
1772
  pages.push({
985
1773
  key: page.key,
986
1774
  id: finalId,
987
1775
  ...page.entry.back !== void 0 ? { back: page.entry.back } : {},
988
1776
  xml,
989
- body: page.body,
990
- shapes,
991
- connects
1777
+ body,
1778
+ shapes: resolved.shapes,
1779
+ shapesXml: resolved.xml,
1780
+ connects,
1781
+ signature: hashTexts([xml, contentSignature]),
1782
+ contentSignature
992
1783
  });
993
1784
  }
1785
+ const mastersDir = mastersPath !== void 0 ? dirName(mastersPath) : pagesDir;
1786
+ const finalMasters = masters.map((m) => {
1787
+ const batch = shared.masters.get(m.key)?.b;
1788
+ const rename = typeof batch === "string" ? renames.get(batch) : void 0;
1789
+ const scope = masterScope(m.key);
1790
+ const resolved = resolveShapes({
1791
+ scope,
1792
+ shapes: shapesByPage.get(scope) ?? /* @__PURE__ */ new Map(),
1793
+ cells: cellsByPage.get(scope) ?? /* @__PURE__ */ new Map(),
1794
+ renum: renumByScope.get(scope) ?? /* @__PURE__ */ new Map(),
1795
+ masterRemap: /* @__PURE__ */ new Map()
1796
+ });
1797
+ fixups.push(...resolved.fixups);
1798
+ return {
1799
+ ...m,
1800
+ ...rename && m.rels !== void 0 ? { rels: renameTargets(m.rels, mastersDir, rename) } : {},
1801
+ shapes: resolved.shapes,
1802
+ shapesXml: resolved.xml,
1803
+ connects: resolveConnects(connectsByPage.get(scope), resolved.shapes)
1804
+ };
1805
+ });
994
1806
  const parts = /* @__PURE__ */ new Map();
995
1807
  for (const [path, value] of shared.parts) if (typeof value === "string") parts.set(path, value);
996
- return { pagesPath, pagesOpen, pagesClose, pages, parts };
1808
+ const baseSignature = hashTexts([
1809
+ pagesPath,
1810
+ pagesOpen,
1811
+ pagesClose,
1812
+ mastersPath ?? "",
1813
+ mastersOpen ?? "",
1814
+ mastersClose ?? "",
1815
+ ...pages.map((p) => `${p.key}:${p.id}:${p.xml}`),
1816
+ ...finalMasters.flatMap((m) => [
1817
+ m.key,
1818
+ m.id,
1819
+ m.xml,
1820
+ m.part,
1821
+ m.rels ?? "\0",
1822
+ m.shapesXml,
1823
+ ...connectSignature(m.connects)
1824
+ ]),
1825
+ ...media.flatMap((m) => [m.path, m.hash, m.declared.type, String(m.declared.byDefault)]),
1826
+ ...[...parts].flatMap(([path, data]) => [path, data])
1827
+ ]);
1828
+ const signature = hashTexts([
1829
+ baseSignature,
1830
+ ...pages.map((p) => `${p.key}:${p.contentSignature}`)
1831
+ ]);
1832
+ return {
1833
+ pagesPath,
1834
+ pagesOpen,
1835
+ pagesClose,
1836
+ pages,
1837
+ ...mastersPath !== void 0 && mastersOpen !== void 0 && mastersClose !== void 0 ? { mastersPath, mastersOpen, mastersClose } : {},
1838
+ masters: finalMasters,
1839
+ media,
1840
+ parts,
1841
+ signature,
1842
+ baseSignature,
1843
+ fixups
1844
+ };
997
1845
  }
998
- function resolveShapes(entries) {
999
- const parsed = entries.map((entry) => ({ ...entry, ids: shapeIds(entry.xml) }));
1000
- let max = Math.max(0, ...parsed.flatMap((p) => p.ids.map(numeric)));
1001
- const used = /* @__PURE__ */ new Set();
1002
- const done = /* @__PURE__ */ new Map();
1003
- for (const item of [...parsed].sort((a, b) => compare(a.key, b.key))) {
1004
- const remap = /* @__PURE__ */ new Map();
1005
- for (const id of item.ids) {
1006
- if (used.has(id) || remap.has(id)) remap.set(id, String(++max));
1846
+
1847
+ // visio/collab/write-shapes.ts
1848
+ function syncEntry(ctx, map, mapKey, id, content, room, roomContent, local, stamp = false) {
1849
+ ctx.now.set(id, content);
1850
+ const base = ctx.keys.known.get(id);
1851
+ const own = base === void 0 ? !room || roomContent() !== content : base !== content;
1852
+ if (own || !room) {
1853
+ const value = local();
1854
+ ctx.write(map, mapKey, stamp ? { ...value, b: ctx.batch } : value);
1855
+ } else {
1856
+ const pos = local().pos;
1857
+ ctx.write(map, mapKey, pos === void 0 ? room : { ...room, pos });
1858
+ }
1859
+ }
1860
+ function groupPositions(items, current) {
1861
+ const groups = /* @__PURE__ */ new Map();
1862
+ for (const item of items) groups.set(item.group, [...groups.get(item.group) ?? [], item.key]);
1863
+ const out = /* @__PURE__ */ new Map();
1864
+ for (const list of groups.values())
1865
+ for (const [key, pos] of assignPositions(list, current)) out.set(key, pos);
1866
+ return out;
1867
+ }
1868
+ function syncConnects(ctx, connects) {
1869
+ for (const connect of connects) {
1870
+ ctx.now.set(connectId(connect.key), "");
1871
+ if (!ctx.keys.known.has(connectId(connect.key)) || !ctx.shared.connects.has(connect.key))
1872
+ ctx.write(ctx.shared.connects, connect.key, connect.entry);
1873
+ }
1874
+ }
1875
+ function syncShapes(ctx, page) {
1876
+ const { shared } = ctx;
1877
+ const shapePos = groupPositions(
1878
+ page.shapes.map((s) => ({ key: s.key, group: s.parent ?? "" })),
1879
+ (key) => shapeEntry(shared.shapes.get(`${page.key}/${key}`))?.pos
1880
+ );
1881
+ for (const shape of page.shapes) {
1882
+ const full = `${page.key}/${shape.key}`;
1883
+ const room = shapeEntry(shared.shapes.get(full));
1884
+ const parent = shape.parent;
1885
+ syncEntry(
1886
+ ctx,
1887
+ shared.shapes,
1888
+ full,
1889
+ shapeId(page.key, shape.key),
1890
+ shellContent({ parent, open: shape.open }),
1891
+ room,
1892
+ () => shellContent(room),
1893
+ () => ({
1894
+ pos: shapePos.get(shape.key),
1895
+ ...parent !== void 0 ? { parent } : {},
1896
+ open: shape.open,
1897
+ b: room?.b ?? ctx.batch
1898
+ })
1899
+ );
1900
+ const piecePos = groupPositions(
1901
+ shape.pieces.map((p) => ({ key: p.path, group: p.parent })),
1902
+ (path) => cellEntry(shared.cells.get(`${full}/${path}`))?.pos
1903
+ );
1904
+ for (const piece of shape.pieces) {
1905
+ const cellKey = `${full}/${piece.path}`;
1906
+ const roomCell = cellEntry(shared.cells.get(cellKey));
1907
+ const local = () => ({
1908
+ pos: piecePos.get(piece.path),
1909
+ ...piece.kind === "leaf" ? { xml: piece.text } : { open: piece.text }
1910
+ });
1911
+ syncEntry(
1912
+ ctx,
1913
+ shared.cells,
1914
+ cellKey,
1915
+ cellId(page.key, shape.key, piece.path),
1916
+ entryContent(piece.kind === "leaf" ? { xml: piece.text } : { open: piece.text }),
1917
+ roomCell,
1918
+ () => entryContent(roomCell),
1919
+ local
1920
+ );
1007
1921
  }
1008
- const ids = item.ids.map((id) => remap.get(id) ?? id);
1009
- for (const id of ids) used.add(id);
1010
- done.set(item.key, {
1011
- key: item.key,
1012
- id: ids[0] ?? "",
1013
- xml: renumberShape(item.xml, remap),
1014
- remap,
1015
- ids
1016
- });
1017
1922
  }
1018
- return byPosition(parsed).map((item) => done.get(item.key));
1923
+ }
1924
+
1925
+ // visio/collab/write-parts.ts
1926
+ var mediaKey = (hash, path) => `${hash}|${path}`;
1927
+ function syncMasters(ctx, snapshot) {
1928
+ const { shared } = ctx;
1929
+ const pos = assignPositions(
1930
+ snapshot.masters.map((m) => m.key),
1931
+ (key) => masterEntry(shared.masters.get(key))?.pos
1932
+ );
1933
+ for (const master of snapshot.masters) {
1934
+ const room = masterEntry(shared.masters.get(master.key));
1935
+ syncEntry(
1936
+ ctx,
1937
+ shared.masters,
1938
+ master.key,
1939
+ masterId(master.key),
1940
+ masterContent({ ...master, print: fingerprint(master.part) }),
1941
+ room,
1942
+ () => masterContent({
1943
+ ...room,
1944
+ print: fingerprint(fromBase64(room.part))
1945
+ }),
1946
+ () => ({
1947
+ pos: pos.get(master.key),
1948
+ id: master.id,
1949
+ xml: master.xml,
1950
+ part: toBase64(master.part),
1951
+ ...master.rels !== void 0 ? { rels: master.rels } : {}
1952
+ }),
1953
+ true
1954
+ );
1955
+ syncShapes(ctx, { key: masterScope(master.key), shapes: master.shapes });
1956
+ syncConnects(ctx, master.connects);
1957
+ }
1958
+ }
1959
+ function syncMedia(ctx, snapshot) {
1960
+ const { shared } = ctx;
1961
+ for (const item of snapshot.media) {
1962
+ const known = ctx.keys.mediaKeys.get(item.path);
1963
+ const key = known?.startsWith(`${item.hash}|`) ? known : mediaKey(item.hash, item.path);
1964
+ ctx.now.set(mediaId(key), "");
1965
+ if (!mediaEntry(shared.media.get(key))) {
1966
+ ctx.write(shared.media, key, {
1967
+ path: item.path,
1968
+ hash: item.hash,
1969
+ ct: item.ct,
1970
+ d: item.d,
1971
+ b: ctx.batch
1972
+ });
1973
+ shared.blobs.set(item.hash, toBase64(item.data));
1974
+ } else if (!shared.blobs.has(item.hash)) shared.blobs.set(item.hash, toBase64(item.data));
1975
+ }
1976
+ }
1977
+ function syncParts(ctx, snapshot) {
1978
+ const { shared, keys } = ctx;
1979
+ for (const [path, data] of snapshot.parts) {
1980
+ const print = fingerprint(data);
1981
+ if (keys.partPrints.get(path) === print) continue;
1982
+ ctx.write(shared.parts, path, toBase64(data));
1983
+ keys.partPrints.set(path, print);
1984
+ }
1985
+ let removed = 0;
1986
+ for (const path of [...keys.partPrints.keys()])
1987
+ if (!snapshot.parts.has(path)) {
1988
+ shared.parts.delete(path);
1989
+ keys.partPrints.delete(path);
1990
+ removed++;
1991
+ }
1992
+ return removed;
1993
+ }
1994
+
1995
+ // visio/collab/write.ts
1996
+ function removeScope(shared, name) {
1997
+ shared.renum.delete(name);
1998
+ for (const map of [shared.shapes, shared.cells, shared.connects, shared.renum])
1999
+ for (const key of [...map.keys()]) if (key.startsWith(`${name}/`)) map.delete(key);
2000
+ }
2001
+ function removePage(shared, name) {
2002
+ shared.dead.set(name, true);
2003
+ for (const map of [shared.pages, shared.bodies]) map.delete(name);
2004
+ removeScope(shared, name);
2005
+ }
2006
+ function writeSharedDrawing(shared, snapshot, keys) {
2007
+ assertSchemaVersion(shared);
2008
+ let writes = 0;
2009
+ const write = (map, key, value) => {
2010
+ if (setIfChanged(map, key, value)) writes++;
2011
+ };
2012
+ const now = /* @__PURE__ */ new Map();
2013
+ const ctx = { shared, keys, now, batch: keys.batch(), write };
2014
+ write(shared.meta, "v", SCHEMA_VERSION);
2015
+ write(shared.meta, "pagesPath", snapshot.pagesPath);
2016
+ write(shared.meta, "pagesOpen", snapshot.pagesOpen);
2017
+ write(shared.meta, "pagesClose", snapshot.pagesClose);
2018
+ if (snapshot.mastersPath !== void 0) {
2019
+ write(shared.meta, "mastersPath", snapshot.mastersPath);
2020
+ write(shared.meta, "mastersOpen", snapshot.mastersOpen);
2021
+ write(shared.meta, "mastersClose", snapshot.mastersClose);
2022
+ }
2023
+ const pagePos = assignPositions(
2024
+ snapshot.pages.map((p) => p.key),
2025
+ (key) => pageEntry(shared.pages.get(key))?.pos
2026
+ );
2027
+ for (const page of snapshot.pages) {
2028
+ const room = pageEntry(shared.pages.get(page.key));
2029
+ syncEntry(
2030
+ ctx,
2031
+ shared.pages,
2032
+ page.key,
2033
+ pageId(page.key),
2034
+ pageContent(page),
2035
+ room,
2036
+ () => pageContent(room),
2037
+ () => ({
2038
+ pos: pagePos.get(page.key),
2039
+ id: page.id,
2040
+ ...page.back !== void 0 ? { back: page.back } : {},
2041
+ xml: page.xml
2042
+ })
2043
+ );
2044
+ const roomBody = bodyEntry(shared.bodies.get(page.key));
2045
+ syncEntry(
2046
+ ctx,
2047
+ shared.bodies,
2048
+ page.key,
2049
+ bodyId(page.key),
2050
+ bodyContent(page),
2051
+ roomBody,
2052
+ () => bodyContent(roomBody),
2053
+ () => ({ shell: page.shell, ...page.rels !== void 0 ? { rels: page.rels } : {} }),
2054
+ true
2055
+ );
2056
+ syncShapes(ctx, page);
2057
+ syncConnects(ctx, page.connects);
2058
+ }
2059
+ syncMasters(ctx, snapshot);
2060
+ syncMedia(ctx, snapshot);
2061
+ for (const id of keys.known.keys()) {
2062
+ if (now.has(id)) continue;
2063
+ const name = id.slice(2);
2064
+ switch (id.slice(0, 2)) {
2065
+ case "p:":
2066
+ removePage(shared, name);
2067
+ keys.forgetPage(name);
2068
+ writes++;
2069
+ break;
2070
+ case "s:": {
2071
+ const slash = name.indexOf("/");
2072
+ const page = name.slice(0, slash);
2073
+ if (!now.has(page.startsWith(masterScope("")) ? masterId(page.slice(2)) : pageId(page)))
2074
+ break;
2075
+ shared.dead.set(name, true);
2076
+ shared.shapes.delete(name);
2077
+ keys.forgetShape(page, name.slice(slash + 1));
2078
+ writes++;
2079
+ break;
2080
+ }
2081
+ case "f:":
2082
+ if (shared.cells.has(name)) {
2083
+ shared.cells.delete(name);
2084
+ writes++;
2085
+ }
2086
+ break;
2087
+ case "c:":
2088
+ if (shared.connects.has(name)) {
2089
+ shared.connects.delete(name);
2090
+ writes++;
2091
+ }
2092
+ break;
2093
+ case "m:":
2094
+ if (shared.masters.has(name)) {
2095
+ shared.masters.delete(name);
2096
+ removeScope(shared, masterScope(name));
2097
+ writes++;
2098
+ }
2099
+ keys.forgetMaster(name);
2100
+ break;
2101
+ case "i:":
2102
+ if (shared.media.has(name)) {
2103
+ shared.media.delete(name);
2104
+ writes++;
2105
+ }
2106
+ break;
2107
+ }
2108
+ }
2109
+ keys.known = now;
2110
+ writes += syncParts(ctx, snapshot);
2111
+ return writes;
1019
2112
  }
1020
2113
 
1021
2114
  // visio/collab/bind.ts
1022
2115
  var VISIO_EDIT_ORIGIN = /* @__PURE__ */ Symbol("ooxml-core:visio:edit");
1023
2116
  var VISIO_SYNC_ORIGIN = /* @__PURE__ */ Symbol("ooxml-core:visio:sync");
2117
+ var VISIO_FIXUP_ORIGIN = /* @__PURE__ */ Symbol("ooxml-core:visio:fixup");
1024
2118
  function bindDrawingSession(host, editor, options = {}) {
1025
2119
  const { doc } = host;
1026
2120
  const shared = sharedDrawing(doc);
@@ -1040,6 +2134,12 @@ function bindDrawingSession(host, editor, options = {}) {
1040
2134
  let settled = false;
1041
2135
  let localDirty = false;
1042
2136
  let remoteDirty = false;
2137
+ let incompatible = false;
2138
+ let queued = false;
2139
+ let queuedDone;
2140
+ const schedule = options.schedule ?? ((run) => {
2141
+ setTimeout(run, 0);
2142
+ });
1043
2143
  let running;
1044
2144
  const report = (error) => options.onError?.(error);
1045
2145
  const publish = async () => {
@@ -1058,7 +2158,14 @@ function bindDrawingSession(host, editor, options = {}) {
1058
2158
  const rebuild = async () => {
1059
2159
  const resolved = readSharedDrawing(shared);
1060
2160
  if (!resolved) return;
2161
+ if (resolved.fixups.length && host.canWrite())
2162
+ doc.transact(() => applyFixups(shared, resolved.fixups), VISIO_FIXUP_ORIGIN);
1061
2163
  const version = editor.version();
2164
+ if (keys.applied?.signature === resolved.signature && keys.applied.version === version) return;
2165
+ const before = keys.applied;
2166
+ const change = before && before.version === version && before.base === resolved.baseSignature ? {
2167
+ pages: resolved.pages.filter((page) => before.pages.get(page.key) !== page.contentSignature).map((page) => page.id)
2168
+ } : void 0;
1062
2169
  const composed = await composeDrawing(resolved, editor.exportBytes(), keys);
1063
2170
  if (disposed) return;
1064
2171
  if (editor.busy() || editor.version() !== version) {
@@ -1066,15 +2173,21 @@ function bindDrawingSession(host, editor, options = {}) {
1066
2173
  return;
1067
2174
  }
1068
2175
  try {
1069
- await editor.applyRemote(composed.bytes);
2176
+ await editor.applyRemote(composed.bytes, change);
1070
2177
  } catch (error) {
1071
2178
  if (editor.busy()) remoteDirty = true;
1072
2179
  else report(error);
1073
2180
  return;
1074
2181
  }
1075
2182
  composed.commit();
2183
+ keys.applied = {
2184
+ signature: composed.signature,
2185
+ version: editor.version(),
2186
+ base: resolved.baseSignature,
2187
+ pages: new Map(resolved.pages.map((page) => [page.key, page.contentSignature]))
2188
+ };
1076
2189
  };
1077
- const blocked = () => disposed || !settled || editor.busy();
2190
+ const blocked = () => disposed || incompatible || !settled || editor.busy();
1078
2191
  const pending = () => !blocked() && (localDirty && host.canWrite() || remoteDirty && !localDirty);
1079
2192
  const loop = async () => {
1080
2193
  while (pending()) {
@@ -1087,6 +2200,7 @@ function bindDrawingSession(host, editor, options = {}) {
1087
2200
  await rebuild();
1088
2201
  }
1089
2202
  } catch (error) {
2203
+ if (error instanceof SchemaVersionError) incompatible = true;
1090
2204
  report(error);
1091
2205
  }
1092
2206
  }
@@ -1113,18 +2227,30 @@ function bindDrawingSession(host, editor, options = {}) {
1113
2227
  }
1114
2228
  pump();
1115
2229
  };
2230
+ const queueRemote = () => {
2231
+ if (queued) return;
2232
+ queued = true;
2233
+ queuedDone = new Promise(
2234
+ (resolve) => schedule(() => {
2235
+ queued = false;
2236
+ remoteDirty = true;
2237
+ resolve();
2238
+ pump();
2239
+ })
2240
+ );
2241
+ };
1116
2242
  const afterTransaction = (transaction) => {
1117
2243
  if (disposed) return;
1118
2244
  const origin = transaction.origin;
1119
- if (origin === VISIO_EDIT_ORIGIN || origin === VISIO_SYNC_ORIGIN) return;
2245
+ if (origin === VISIO_EDIT_ORIGIN || origin === VISIO_SYNC_ORIGIN || origin === VISIO_FIXUP_ORIGIN)
2246
+ return;
1120
2247
  const changed = transaction.changed;
1121
2248
  if (!types.some((type) => changed.has(type))) return;
1122
2249
  if (!settled) {
1123
2250
  if (!isSharedDrawingEmpty(shared)) settle();
1124
2251
  return;
1125
2252
  }
1126
- remoteDirty = true;
1127
- pump();
2253
+ queueRemote();
1128
2254
  };
1129
2255
  doc.on("afterTransaction", afterTransaction);
1130
2256
  const stops = [
@@ -1142,7 +2268,10 @@ function bindDrawingSession(host, editor, options = {}) {
1142
2268
  ];
1143
2269
  if (host.synced || host.canWrite()) settle();
1144
2270
  const idle = async () => {
1145
- while (running) await running;
2271
+ while (running || queued) {
2272
+ if (queued) await queuedDone;
2273
+ if (running) await running;
2274
+ }
1146
2275
  };
1147
2276
  const replay = async (kind) => {
1148
2277
  await idle();
@@ -1174,17 +2303,25 @@ function bindDrawingSession(host, editor, options = {}) {
1174
2303
  };
1175
2304
  }
1176
2305
 
2306
+ exports.BLOBS = BLOBS;
1177
2307
  exports.BODIES = BODIES;
2308
+ exports.CELLS = CELLS;
1178
2309
  exports.CONNECTS = CONNECTS;
1179
2310
  exports.DEAD = DEAD;
1180
2311
  exports.DrawingKeys = DrawingKeys;
2312
+ exports.MASTERS = MASTERS;
2313
+ exports.MEDIA = MEDIA;
1181
2314
  exports.META = META;
1182
2315
  exports.PAGES = PAGES;
1183
2316
  exports.PARTS = PARTS;
2317
+ exports.RENUM = RENUM;
1184
2318
  exports.SCHEMA_VERSION = SCHEMA_VERSION;
1185
2319
  exports.SHAPES = SHAPES;
2320
+ exports.SchemaVersionError = SchemaVersionError;
1186
2321
  exports.VISIO_EDIT_ORIGIN = VISIO_EDIT_ORIGIN;
2322
+ exports.VISIO_FIXUP_ORIGIN = VISIO_FIXUP_ORIGIN;
1187
2323
  exports.VISIO_SYNC_ORIGIN = VISIO_SYNC_ORIGIN;
2324
+ exports.applyFixups = applyFixups;
1188
2325
  exports.bindDrawingSession = bindDrawingSession;
1189
2326
  exports.composeDrawing = composeDrawing;
1190
2327
  exports.isSharedDrawingEmpty = isSharedDrawingEmpty;