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