docx-diff-editor 1.0.42 → 1.0.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -495,8 +495,10 @@ async function parseHtmlToJson(html, SuperDoc) {
495
495
  try {
496
496
  superdoc = new SuperDoc({
497
497
  selector: container,
498
- html,
499
- documentMode: "viewing",
498
+ html: "<p></p>",
499
+ // Minimal empty document
500
+ documentMode: "editing",
501
+ // Need editing mode to use paste
500
502
  rulers: false,
501
503
  user: { name: "Parser", email: "parser@local" },
502
504
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -507,10 +509,26 @@ async function parseHtmlToJson(html, SuperDoc) {
507
509
  if (!editor) {
508
510
  throw new Error("No active editor found");
509
511
  }
510
- const json = editor.getJSON();
511
- resolved = true;
512
- cleanup();
513
- resolve(json);
512
+ const view = editor.view;
513
+ if (!view) {
514
+ throw new Error("No editor view found");
515
+ }
516
+ editor.commands.selectAll();
517
+ editor.commands.deleteSelection();
518
+ view.pasteHTML(html);
519
+ setTimeout(() => {
520
+ if (resolved) return;
521
+ try {
522
+ const json = editor.getJSON();
523
+ resolved = true;
524
+ cleanup();
525
+ resolve(json);
526
+ } catch (err) {
527
+ resolved = true;
528
+ cleanup();
529
+ reject(err);
530
+ }
531
+ }, 50);
514
532
  } catch (err) {
515
533
  resolved = true;
516
534
  cleanup();
@@ -781,204 +799,6 @@ function diffDocuments(docA, docB) {
781
799
  summary
782
800
  };
783
801
  }
784
- function createTrackInsertMark(author = DEFAULT_AUTHOR, id) {
785
- return {
786
- type: "trackInsert",
787
- attrs: {
788
- id: id ?? uuid.v4(),
789
- author: author.name,
790
- authorEmail: author.email,
791
- authorImage: "",
792
- date: (/* @__PURE__ */ new Date()).toISOString()
793
- }
794
- };
795
- }
796
- function createTrackDeleteMark(author = DEFAULT_AUTHOR, id) {
797
- return {
798
- type: "trackDelete",
799
- attrs: {
800
- id: id ?? uuid.v4(),
801
- author: author.name,
802
- authorEmail: author.email,
803
- authorImage: "",
804
- date: (/* @__PURE__ */ new Date()).toISOString()
805
- }
806
- };
807
- }
808
- function createTrackFormatMark(before, after, author = DEFAULT_AUTHOR) {
809
- return {
810
- type: "trackFormat",
811
- attrs: {
812
- id: uuid.v4(),
813
- author: author.name,
814
- authorEmail: author.email,
815
- authorImage: "",
816
- date: (/* @__PURE__ */ new Date()).toISOString(),
817
- before,
818
- after
819
- }
820
- };
821
- }
822
-
823
- // src/services/mergeDocuments.ts
824
- function cloneNode(node) {
825
- return JSON.parse(JSON.stringify(node));
826
- }
827
- function mergeDocuments(docA, docB, diffResult, author = DEFAULT_AUTHOR) {
828
- const merged = cloneNode(docA);
829
- const charStates = [];
830
- let insertions = [];
831
- const formatChanges = diffResult.formatChanges || [];
832
- function getFormatChangeAt(pos) {
833
- for (const fc of formatChanges) {
834
- if (pos >= fc.from && pos < fc.to) {
835
- return fc;
836
- }
837
- }
838
- return null;
839
- }
840
- let docAOffset = 0;
841
- const segments = diffResult.segments;
842
- for (let segIdx = 0; segIdx < segments.length; segIdx++) {
843
- const segment = segments[segIdx];
844
- if (segment.type === "equal") {
845
- for (let i = 0; i < segment.text.length; i++) {
846
- charStates[docAOffset + i] = { type: "equal" };
847
- }
848
- docAOffset += segment.text.length;
849
- } else if (segment.type === "delete") {
850
- const nextSegment = segments[segIdx + 1];
851
- const isReplacement = nextSegment && nextSegment.type === "insert";
852
- const replacementId = isReplacement ? uuid.v4() : void 0;
853
- for (let i = 0; i < segment.text.length; i++) {
854
- charStates[docAOffset + i] = { type: "delete", replacementId };
855
- }
856
- docAOffset += segment.text.length;
857
- if (isReplacement && nextSegment) {
858
- insertions.push({
859
- afterOffset: docAOffset,
860
- text: nextSegment.text,
861
- replacementId
862
- });
863
- segIdx++;
864
- }
865
- } else if (segment.type === "insert") {
866
- insertions.push({
867
- afterOffset: docAOffset,
868
- text: segment.text
869
- });
870
- }
871
- }
872
- function transformNode(node, nodeOffset, path) {
873
- if (node.type === "text" && node.text) {
874
- const text = node.text;
875
- const result = [];
876
- let i = 0;
877
- while (i < text.length) {
878
- const charOffset = nodeOffset + i;
879
- const charState = charStates[charOffset] || { type: "equal" };
880
- const insertionsHere = insertions.filter((ins) => ins.afterOffset === charOffset);
881
- for (const ins of insertionsHere) {
882
- result.push({
883
- type: "text",
884
- text: ins.text,
885
- marks: [...node.marks || [], createTrackInsertMark(author, ins.replacementId)]
886
- });
887
- }
888
- const currentFormatChange = getFormatChangeAt(nodeOffset + i);
889
- let j = i + 1;
890
- while (j < text.length) {
891
- const nextState = charStates[nodeOffset + j] || { type: "equal" };
892
- if (nextState.type !== charState.type) break;
893
- if (insertions.some((ins) => ins.afterOffset === nodeOffset + j)) break;
894
- const nextFormatChange = getFormatChangeAt(nodeOffset + j);
895
- if (currentFormatChange !== nextFormatChange) break;
896
- j++;
897
- }
898
- const chunk = text.substring(i, j);
899
- let marks = [...node.marks || []];
900
- if (charState.type === "delete") {
901
- marks.push(createTrackDeleteMark(author, charState.replacementId));
902
- } else if (charState.type === "equal") {
903
- if (currentFormatChange) {
904
- const trackFormatMark = createTrackFormatMark(
905
- currentFormatChange.before,
906
- currentFormatChange.after,
907
- author
908
- );
909
- marks = [...currentFormatChange.after, trackFormatMark];
910
- }
911
- }
912
- result.push({
913
- type: "text",
914
- text: chunk,
915
- marks: marks.length > 0 ? marks : void 0
916
- });
917
- i = j;
918
- }
919
- const endOffset = nodeOffset + text.length;
920
- const endInsertions = insertions.filter((ins) => ins.afterOffset === endOffset);
921
- for (const ins of endInsertions) {
922
- result.push({
923
- type: "text",
924
- text: ins.text,
925
- marks: [...node.marks || [], createTrackInsertMark(author, ins.replacementId)]
926
- });
927
- }
928
- insertions = insertions.filter(
929
- (ins) => ins.afterOffset < nodeOffset || ins.afterOffset > endOffset
930
- );
931
- return { nodes: result, consumedLength: text.length };
932
- }
933
- if (node.content && Array.isArray(node.content)) {
934
- const newContent = [];
935
- let offset = nodeOffset;
936
- for (const child of node.content) {
937
- const { nodes, consumedLength } = transformNode(child, offset);
938
- newContent.push(...nodes);
939
- offset += consumedLength;
940
- }
941
- return {
942
- nodes: [{ ...node, content: newContent }],
943
- consumedLength: offset - nodeOffset
944
- };
945
- }
946
- return { nodes: [node], consumedLength: 0 };
947
- }
948
- if (merged.content && Array.isArray(merged.content)) {
949
- const newContent = [];
950
- let offset = 0;
951
- for (let i = 0; i < merged.content.length; i++) {
952
- const child = merged.content[i];
953
- const { nodes, consumedLength } = transformNode(child, offset);
954
- newContent.push(...nodes);
955
- offset += consumedLength;
956
- }
957
- merged.content = newContent;
958
- }
959
- if (insertions.length > 0) {
960
- for (const ins of insertions) {
961
- const insertNode = {
962
- type: "paragraph",
963
- content: [
964
- {
965
- type: "run",
966
- content: [
967
- {
968
- type: "text",
969
- text: ins.text,
970
- marks: [createTrackInsertMark(author, ins.replacementId)]
971
- }
972
- ]
973
- }
974
- ]
975
- };
976
- if (!merged.content) merged.content = [];
977
- merged.content.push(insertNode);
978
- }
979
- }
980
- return merged;
981
- }
982
802
 
983
803
  // src/services/changeContextExtractor.ts
984
804
  function extractEnrichedChanges(mergedJson) {
@@ -1237,6 +1057,44 @@ function groupReplacements(changes) {
1237
1057
  }
1238
1058
  return result;
1239
1059
  }
1060
+ function createTrackInsertMark(author = DEFAULT_AUTHOR, id) {
1061
+ return {
1062
+ type: "trackInsert",
1063
+ attrs: {
1064
+ id: id ?? uuid.v4(),
1065
+ author: author.name,
1066
+ authorEmail: author.email,
1067
+ authorImage: "",
1068
+ date: (/* @__PURE__ */ new Date()).toISOString()
1069
+ }
1070
+ };
1071
+ }
1072
+ function createTrackDeleteMark(author = DEFAULT_AUTHOR, id) {
1073
+ return {
1074
+ type: "trackDelete",
1075
+ attrs: {
1076
+ id: id ?? uuid.v4(),
1077
+ author: author.name,
1078
+ authorEmail: author.email,
1079
+ authorImage: "",
1080
+ date: (/* @__PURE__ */ new Date()).toISOString()
1081
+ }
1082
+ };
1083
+ }
1084
+ function createTrackFormatMark(before, after, author = DEFAULT_AUTHOR) {
1085
+ return {
1086
+ type: "trackFormat",
1087
+ attrs: {
1088
+ id: uuid.v4(),
1089
+ author: author.name,
1090
+ authorEmail: author.email,
1091
+ authorImage: "",
1092
+ date: (/* @__PURE__ */ new Date()).toISOString(),
1093
+ before,
1094
+ after
1095
+ }
1096
+ };
1097
+ }
1240
1098
 
1241
1099
  // src/services/nodeAligner.ts
1242
1100
  init_nodeFingerprint();
@@ -1417,19 +1275,177 @@ function alignListItems(listA, listB, listPathA, listPathB) {
1417
1275
  }
1418
1276
  return alignNodes(itemsA, itemsB);
1419
1277
  }
1420
-
1421
- // src/services/attrComparer.ts
1422
- var KNOWN_DEFAULTS = {
1423
- paragraph: {
1424
- textAlign: "left",
1425
- indent: 0,
1426
- lineSpacing: 1
1427
- },
1428
- heading: {
1429
- level: 1,
1430
- textAlign: "left"
1431
- },
1432
- table: {
1278
+ function cloneNode(node) {
1279
+ return JSON.parse(JSON.stringify(node));
1280
+ }
1281
+ function mergeDocuments(docA, docB, diffResult, author = DEFAULT_AUTHOR) {
1282
+ const merged = cloneNode(docA);
1283
+ const charStates = [];
1284
+ let insertions = [];
1285
+ const formatChanges = diffResult.formatChanges || [];
1286
+ function getFormatChangeAt(pos) {
1287
+ for (const fc of formatChanges) {
1288
+ if (pos >= fc.from && pos < fc.to) {
1289
+ return fc;
1290
+ }
1291
+ }
1292
+ return null;
1293
+ }
1294
+ let docAOffset = 0;
1295
+ const segments = diffResult.segments;
1296
+ for (let segIdx = 0; segIdx < segments.length; segIdx++) {
1297
+ const segment = segments[segIdx];
1298
+ if (segment.type === "equal") {
1299
+ for (let i = 0; i < segment.text.length; i++) {
1300
+ charStates[docAOffset + i] = { type: "equal" };
1301
+ }
1302
+ docAOffset += segment.text.length;
1303
+ } else if (segment.type === "delete") {
1304
+ const nextSegment = segments[segIdx + 1];
1305
+ const isReplacement = nextSegment && nextSegment.type === "insert";
1306
+ const replacementId = isReplacement ? uuid.v4() : void 0;
1307
+ for (let i = 0; i < segment.text.length; i++) {
1308
+ charStates[docAOffset + i] = { type: "delete", replacementId };
1309
+ }
1310
+ docAOffset += segment.text.length;
1311
+ if (isReplacement && nextSegment) {
1312
+ insertions.push({
1313
+ afterOffset: docAOffset,
1314
+ text: nextSegment.text,
1315
+ replacementId
1316
+ });
1317
+ segIdx++;
1318
+ }
1319
+ } else if (segment.type === "insert") {
1320
+ insertions.push({
1321
+ afterOffset: docAOffset,
1322
+ text: segment.text
1323
+ });
1324
+ }
1325
+ }
1326
+ function transformNode(node, nodeOffset, path) {
1327
+ if (node.type === "text" && node.text) {
1328
+ const text = node.text;
1329
+ const result = [];
1330
+ let i = 0;
1331
+ while (i < text.length) {
1332
+ const charOffset = nodeOffset + i;
1333
+ const charState = charStates[charOffset] || { type: "equal" };
1334
+ const insertionsHere = insertions.filter((ins) => ins.afterOffset === charOffset);
1335
+ for (const ins of insertionsHere) {
1336
+ result.push({
1337
+ type: "text",
1338
+ text: ins.text,
1339
+ marks: [...node.marks || [], createTrackInsertMark(author, ins.replacementId)]
1340
+ });
1341
+ }
1342
+ const currentFormatChange = getFormatChangeAt(nodeOffset + i);
1343
+ let j = i + 1;
1344
+ while (j < text.length) {
1345
+ const nextState = charStates[nodeOffset + j] || { type: "equal" };
1346
+ if (nextState.type !== charState.type) break;
1347
+ if (insertions.some((ins) => ins.afterOffset === nodeOffset + j)) break;
1348
+ const nextFormatChange = getFormatChangeAt(nodeOffset + j);
1349
+ if (currentFormatChange !== nextFormatChange) break;
1350
+ j++;
1351
+ }
1352
+ const chunk = text.substring(i, j);
1353
+ let marks = [...node.marks || []];
1354
+ if (charState.type === "delete") {
1355
+ marks.push(createTrackDeleteMark(author, charState.replacementId));
1356
+ } else if (charState.type === "equal") {
1357
+ if (currentFormatChange) {
1358
+ const trackFormatMark = createTrackFormatMark(
1359
+ currentFormatChange.before,
1360
+ currentFormatChange.after,
1361
+ author
1362
+ );
1363
+ marks = [...currentFormatChange.after, trackFormatMark];
1364
+ }
1365
+ }
1366
+ result.push({
1367
+ type: "text",
1368
+ text: chunk,
1369
+ marks: marks.length > 0 ? marks : void 0
1370
+ });
1371
+ i = j;
1372
+ }
1373
+ const endOffset = nodeOffset + text.length;
1374
+ const endInsertions = insertions.filter((ins) => ins.afterOffset === endOffset);
1375
+ for (const ins of endInsertions) {
1376
+ result.push({
1377
+ type: "text",
1378
+ text: ins.text,
1379
+ marks: [...node.marks || [], createTrackInsertMark(author, ins.replacementId)]
1380
+ });
1381
+ }
1382
+ insertions = insertions.filter(
1383
+ (ins) => ins.afterOffset < nodeOffset || ins.afterOffset > endOffset
1384
+ );
1385
+ return { nodes: result, consumedLength: text.length };
1386
+ }
1387
+ if (node.content && Array.isArray(node.content)) {
1388
+ const newContent = [];
1389
+ let offset = nodeOffset;
1390
+ for (const child of node.content) {
1391
+ const { nodes, consumedLength } = transformNode(child, offset);
1392
+ newContent.push(...nodes);
1393
+ offset += consumedLength;
1394
+ }
1395
+ return {
1396
+ nodes: [{ ...node, content: newContent }],
1397
+ consumedLength: offset - nodeOffset
1398
+ };
1399
+ }
1400
+ return { nodes: [node], consumedLength: 0 };
1401
+ }
1402
+ if (merged.content && Array.isArray(merged.content)) {
1403
+ const newContent = [];
1404
+ let offset = 0;
1405
+ for (let i = 0; i < merged.content.length; i++) {
1406
+ const child = merged.content[i];
1407
+ const { nodes, consumedLength } = transformNode(child, offset);
1408
+ newContent.push(...nodes);
1409
+ offset += consumedLength;
1410
+ }
1411
+ merged.content = newContent;
1412
+ }
1413
+ if (insertions.length > 0) {
1414
+ for (const ins of insertions) {
1415
+ const insertNode = {
1416
+ type: "paragraph",
1417
+ content: [
1418
+ {
1419
+ type: "run",
1420
+ content: [
1421
+ {
1422
+ type: "text",
1423
+ text: ins.text,
1424
+ marks: [createTrackInsertMark(author, ins.replacementId)]
1425
+ }
1426
+ ]
1427
+ }
1428
+ ]
1429
+ };
1430
+ if (!merged.content) merged.content = [];
1431
+ merged.content.push(insertNode);
1432
+ }
1433
+ }
1434
+ return merged;
1435
+ }
1436
+
1437
+ // src/services/attrComparer.ts
1438
+ var KNOWN_DEFAULTS = {
1439
+ paragraph: {
1440
+ textAlign: "left",
1441
+ indent: 0,
1442
+ lineSpacing: 1
1443
+ },
1444
+ heading: {
1445
+ level: 1,
1446
+ textAlign: "left"
1447
+ },
1448
+ table: {
1433
1449
  alignment: "left",
1434
1450
  borderStyle: "single"
1435
1451
  },
@@ -1649,6 +1665,9 @@ function diffTables(tableA, tableB, tablePathA, tablePathB) {
1649
1665
  function isTable(node) {
1650
1666
  return node?.type === "table";
1651
1667
  }
1668
+ function isTableRow(node) {
1669
+ return node?.type === "tableRow";
1670
+ }
1652
1671
  function getRowLocation(tablePath, rowIndex, tableIndex) {
1653
1672
  return `Table ${tableIndex + 1}, Row ${rowIndex + 1}`;
1654
1673
  }
@@ -1685,6 +1704,9 @@ function extractCellText(cell) {
1685
1704
  function isList(node) {
1686
1705
  return node?.type === "bulletList" || node?.type === "orderedList";
1687
1706
  }
1707
+ function isListItem(node) {
1708
+ return node?.type === "listItem";
1709
+ }
1688
1710
  function extractListItemText(item) {
1689
1711
  const texts = [];
1690
1712
  function extract(node) {
@@ -1917,7 +1939,10 @@ function getImagePreview(node) {
1917
1939
  return "(image)";
1918
1940
  }
1919
1941
 
1920
- // src/services/blockLevelMerger.ts
1942
+ // src/services/structuralMerger.ts
1943
+ function cloneNode2(node) {
1944
+ return JSON.parse(JSON.stringify(node));
1945
+ }
1921
1946
  function markAllTextAsInserted(node, sharedId, author) {
1922
1947
  if (node.type === "text") {
1923
1948
  return {
@@ -1933,7 +1958,7 @@ function markAllTextAsInserted(node, sharedId, author) {
1933
1958
  )
1934
1959
  };
1935
1960
  }
1936
- return node;
1961
+ return { ...node };
1937
1962
  }
1938
1963
  function markAllTextAsDeleted(node, sharedId, author) {
1939
1964
  if (node.type === "text") {
@@ -1950,10 +1975,7 @@ function markAllTextAsDeleted(node, sharedId, author) {
1950
1975
  )
1951
1976
  };
1952
1977
  }
1953
- return node;
1954
- }
1955
- function cloneNode2(node) {
1956
- return JSON.parse(JSON.stringify(node));
1978
+ return { ...node };
1957
1979
  }
1958
1980
  function extractTextPreview(node, maxLength = 50) {
1959
1981
  const texts = [];
@@ -1974,197 +1996,361 @@ function extractTextPreview(node, maxLength = 50) {
1974
1996
  }
1975
1997
  return text || "(empty)";
1976
1998
  }
1977
- function processStructuralChanges(docA, docB, author = DEFAULT_AUTHOR) {
1978
- const changes = [];
1979
- const infos = [];
1999
+ function getNodeTypeDescription(node) {
2000
+ if (isTable(node)) return "Table";
2001
+ if (isList(node)) return "List";
2002
+ if (isListItem(node)) return "List item";
2003
+ if (isTableRow(node)) return "Table row";
2004
+ if (isImage(node)) return "Image";
2005
+ if (node.type === "heading") return `Heading ${node.attrs?.level || 1}`;
2006
+ if (node.type === "paragraph") return "Paragraph";
2007
+ if (node.type === "blockquote") return "Blockquote";
2008
+ if (node.type === "codeBlock") return "Code block";
2009
+ return node.type || "Block";
2010
+ }
2011
+ function mergeWithStructuralAwareness(docA, docB, author = DEFAULT_AUTHOR) {
2012
+ const structuralInfos = [];
2013
+ const summary = [];
2014
+ let textChangeCount = 0;
1980
2015
  const alignment = alignDocuments(docA, docB);
1981
- let tableIndex = 0;
1982
- let listIndex = 0;
1983
- let paragraphIndex = 0;
1984
- for (const inserted of alignment.insertions) {
1985
- const node = inserted.node;
1986
- const sharedId = uuid.v4();
1987
- const date = (/* @__PURE__ */ new Date()).toISOString();
1988
- let type = "paragraphInsert";
1989
- let location = "";
1990
- let preview = "";
1991
- if (isTable(node)) {
1992
- type = "rowInsert";
1993
- location = `New table at position ${inserted.path[0] + 1}`;
1994
- preview = `Table with ${node.content?.length || 0} rows`;
1995
- tableIndex++;
1996
- } else if (isList(node)) {
1997
- type = "listItemInsert";
1998
- location = `New list at position ${inserted.path[0] + 1}`;
1999
- preview = `List with ${node.content?.length || 0} items`;
2000
- listIndex++;
2001
- } else {
2002
- type = "paragraphInsert";
2003
- paragraphIndex++;
2004
- location = `Paragraph ${paragraphIndex}`;
2005
- preview = extractTextPreview(node);
2016
+ const operations = buildMergeOperations(alignment, docA, docB);
2017
+ const mergedContent = [];
2018
+ let blockIndex = 0;
2019
+ for (const op of operations) {
2020
+ blockIndex++;
2021
+ switch (op.type) {
2022
+ case "matched": {
2023
+ const { mergedNode, infos, changes } = mergeMatchedBlock(
2024
+ op.nodeA,
2025
+ op.nodeB,
2026
+ blockIndex,
2027
+ author
2028
+ );
2029
+ mergedContent.push(mergedNode);
2030
+ structuralInfos.push(...infos);
2031
+ textChangeCount += changes;
2032
+ break;
2033
+ }
2034
+ case "inserted": {
2035
+ const { markedNode, info } = createInsertedBlock(
2036
+ op.nodeB,
2037
+ blockIndex,
2038
+ author
2039
+ );
2040
+ mergedContent.push(markedNode);
2041
+ if (info) {
2042
+ structuralInfos.push(info);
2043
+ }
2044
+ break;
2045
+ }
2046
+ case "deleted": {
2047
+ const { markedNode, info } = createDeletedBlock(
2048
+ op.nodeA,
2049
+ blockIndex,
2050
+ author
2051
+ );
2052
+ mergedContent.push(markedNode);
2053
+ if (info) {
2054
+ structuralInfos.push(info);
2055
+ }
2056
+ break;
2057
+ }
2006
2058
  }
2007
- changes.push({
2008
- id: sharedId,
2009
- type,
2010
- nodeType: node.type,
2011
- path: inserted.path,
2012
- node: markAllTextAsInserted(cloneNode2(node), sharedId, author)
2013
- });
2014
- infos.push({
2015
- id: sharedId,
2016
- type,
2017
- nodeType: node.type,
2018
- location,
2019
- preview,
2020
- author,
2021
- date
2022
- });
2023
2059
  }
2024
- for (const deleted of alignment.deletions) {
2025
- const node = deleted.node;
2026
- const sharedId = uuid.v4();
2027
- const date = (/* @__PURE__ */ new Date()).toISOString();
2028
- let type = "paragraphDelete";
2029
- let location = "";
2030
- let preview = "";
2031
- if (isTable(node)) {
2032
- type = "rowDelete";
2033
- location = `Deleted table at position ${deleted.path[0] + 1}`;
2034
- preview = `Table with ${node.content?.length || 0} rows`;
2035
- } else if (isList(node)) {
2036
- type = "listItemDelete";
2037
- location = `Deleted list at position ${deleted.path[0] + 1}`;
2038
- preview = `List with ${node.content?.length || 0} items`;
2060
+ const mergedDoc = {
2061
+ type: "doc",
2062
+ content: mergedContent
2063
+ };
2064
+ const insertCount = structuralInfos.filter((i) => i.type.includes("Insert")).length;
2065
+ const deleteCount = structuralInfos.filter((i) => i.type.includes("Delete")).length;
2066
+ if (insertCount > 0) summary.push(`${insertCount} block(s) inserted`);
2067
+ if (deleteCount > 0) summary.push(`${deleteCount} block(s) deleted`);
2068
+ if (textChangeCount > 0) summary.push(`${textChangeCount} text change(s)`);
2069
+ return {
2070
+ mergedDoc,
2071
+ structuralInfos,
2072
+ summary,
2073
+ textChangeCount
2074
+ };
2075
+ }
2076
+ function buildMergeOperations(alignment, docA, docB) {
2077
+ const operations = [];
2078
+ const matchedFromA = /* @__PURE__ */ new Map();
2079
+ const matchedFromB = /* @__PURE__ */ new Map();
2080
+ for (const match of alignment.matched) {
2081
+ const idxA = match.pathA[0];
2082
+ const idxB = match.pathB[0];
2083
+ matchedFromA.set(idxA, { pathB: match.pathB, similarity: match.similarity });
2084
+ matchedFromB.set(idxB, { pathA: match.pathA, similarity: match.similarity });
2085
+ }
2086
+ const deletedIndices = new Set(alignment.deletions.map((d) => d.path[0]));
2087
+ const processedDeletions = /* @__PURE__ */ new Set();
2088
+ const contentB = docB.content || [];
2089
+ const contentA = docA.content || [];
2090
+ for (let idxB = 0; idxB < contentB.length; idxB++) {
2091
+ const nodeB = contentB[idxB];
2092
+ const match = matchedFromB.get(idxB);
2093
+ if (match) {
2094
+ const idxA = match.pathA[0];
2095
+ const nodeA = contentA[idxA];
2096
+ for (let checkIdx = 0; checkIdx < idxA; checkIdx++) {
2097
+ if (deletedIndices.has(checkIdx) && !processedDeletions.has(checkIdx)) {
2098
+ operations.push({
2099
+ type: "deleted",
2100
+ nodeA: contentA[checkIdx],
2101
+ pathA: [checkIdx]
2102
+ });
2103
+ processedDeletions.add(checkIdx);
2104
+ }
2105
+ }
2106
+ operations.push({
2107
+ type: "matched",
2108
+ nodeA,
2109
+ nodeB,
2110
+ pathA: match.pathA,
2111
+ pathB: [idxB]
2112
+ });
2039
2113
  } else {
2040
- type = "paragraphDelete";
2041
- location = `Deleted paragraph`;
2042
- preview = extractTextPreview(node);
2114
+ operations.push({
2115
+ type: "inserted",
2116
+ nodeB,
2117
+ pathB: [idxB]
2118
+ });
2043
2119
  }
2044
- changes.push({
2045
- id: sharedId,
2046
- type,
2047
- nodeType: node.type,
2048
- path: deleted.path,
2049
- node: markAllTextAsDeleted(cloneNode2(node), sharedId, author)
2050
- });
2051
- infos.push({
2052
- id: sharedId,
2053
- type,
2054
- nodeType: node.type,
2055
- location,
2056
- preview,
2057
- author,
2058
- date
2059
- });
2060
2120
  }
2061
- for (const match of alignment.matched) {
2062
- const nodeA = docA.content?.[match.pathA[0]];
2063
- const nodeB = docB.content?.[match.pathB[0]];
2064
- if (!nodeA || !nodeB) continue;
2065
- if (isTable(nodeA) && isTable(nodeB)) {
2066
- tableIndex++;
2067
- const tableResult = diffTables(nodeA, nodeB, match.pathA, match.pathB);
2068
- for (const rowChange of tableResult.rowChanges) {
2069
- const sharedId = rowChange.id;
2070
- const date = (/* @__PURE__ */ new Date()).toISOString();
2071
- const rowIndex = rowChange.path[rowChange.path.length - 1];
2072
- const isInsert = rowChange.type === "rowInsert";
2073
- const location = getRowLocation(rowChange.path, rowIndex, tableIndex - 1);
2074
- const preview = getRowPreview(rowChange.node);
2075
- const markedNode = isInsert ? markAllTextAsInserted(cloneNode2(rowChange.node), sharedId, author) : markAllTextAsDeleted(cloneNode2(rowChange.node), sharedId, author);
2076
- changes.push({
2077
- ...rowChange,
2078
- node: markedNode
2079
- });
2080
- infos.push({
2081
- id: sharedId,
2082
- type: rowChange.type,
2083
- nodeType: "tableRow",
2084
- location,
2085
- preview,
2086
- author,
2087
- date
2088
- });
2089
- }
2121
+ for (const deletion of alignment.deletions) {
2122
+ const idxA = deletion.path[0];
2123
+ if (!processedDeletions.has(idxA)) {
2124
+ operations.push({
2125
+ type: "deleted",
2126
+ nodeA: deletion.node,
2127
+ pathA: deletion.path
2128
+ });
2090
2129
  }
2091
- if (isList(nodeA) && isList(nodeB)) {
2092
- listIndex++;
2093
- const listResult = diffLists(nodeA, nodeB, match.pathA, match.pathB);
2094
- for (const itemChange of listResult.itemChanges) {
2095
- const sharedId = itemChange.id;
2096
- const date = (/* @__PURE__ */ new Date()).toISOString();
2097
- const itemIndex = itemChange.path[itemChange.path.length - 1];
2098
- const isInsert = itemChange.type === "listItemInsert";
2099
- const location = getListItemLocation(itemChange.path, itemIndex, listIndex - 1);
2100
- const preview = getListItemPreview(itemChange.node);
2101
- const markedNode = isInsert ? markAllTextAsInserted(cloneNode2(itemChange.node), sharedId, author) : markAllTextAsDeleted(cloneNode2(itemChange.node), sharedId, author);
2102
- changes.push({
2103
- ...itemChange,
2104
- node: markedNode
2105
- });
2106
- infos.push({
2107
- id: sharedId,
2108
- type: itemChange.type,
2109
- nodeType: "listItem",
2110
- location,
2111
- preview,
2112
- author,
2113
- date
2114
- });
2130
+ }
2131
+ return operations;
2132
+ }
2133
+ function mergeMatchedBlock(nodeA, nodeB, blockIndex, author) {
2134
+ const infos = [];
2135
+ let changes = 0;
2136
+ if (isTable(nodeA) && isTable(nodeB)) {
2137
+ const { mergedTable, tableInfos, changeCount } = mergeMatchedTable(
2138
+ nodeA,
2139
+ nodeB,
2140
+ blockIndex,
2141
+ author
2142
+ );
2143
+ return { mergedNode: mergedTable, infos: tableInfos, changes: changeCount };
2144
+ }
2145
+ if (isList(nodeA) && isList(nodeB)) {
2146
+ const { mergedList, listInfos, changeCount } = mergeMatchedList(
2147
+ nodeA,
2148
+ nodeB,
2149
+ blockIndex,
2150
+ author
2151
+ );
2152
+ return { mergedNode: mergedList, infos: listInfos, changes: changeCount };
2153
+ }
2154
+ const diff = diffDocuments(
2155
+ { type: "doc", content: [nodeA] },
2156
+ { type: "doc", content: [nodeB] }
2157
+ );
2158
+ changes = diff.segments.filter((s) => s.type !== "equal").length;
2159
+ changes += diff.formatChanges?.length || 0;
2160
+ const merged = mergeDocuments(
2161
+ { type: "doc", content: [nodeA] },
2162
+ { },
2163
+ diff,
2164
+ author
2165
+ );
2166
+ const mergedNode = merged.content?.[0] || cloneNode2(nodeB);
2167
+ return { mergedNode, infos, changes };
2168
+ }
2169
+ function mergeMatchedTable(tableA, tableB, tableIndex, author) {
2170
+ const tableInfos = [];
2171
+ let changeCount = 0;
2172
+ const rowAlignment = alignTableRows(tableA, tableB, [tableIndex - 1], [tableIndex - 1]);
2173
+ const mergedRows = [];
2174
+ const matchedFromA = /* @__PURE__ */ new Map();
2175
+ const matchedFromB = /* @__PURE__ */ new Map();
2176
+ for (const match of rowAlignment.matched) {
2177
+ const idxA = match.pathA[match.pathA.length - 1];
2178
+ const idxB = match.pathB[match.pathB.length - 1];
2179
+ matchedFromA.set(idxA, idxB);
2180
+ matchedFromB.set(idxB, idxA);
2181
+ }
2182
+ const deletedIndices = new Set(rowAlignment.deletions.map((d) => d.path[d.path.length - 1]));
2183
+ const processedDeletions = /* @__PURE__ */ new Set();
2184
+ const rowsA = tableA.content || [];
2185
+ const rowsB = tableB.content || [];
2186
+ for (let idxB = 0; idxB < rowsB.length; idxB++) {
2187
+ const rowB = rowsB[idxB];
2188
+ const matchedIdxA = matchedFromB.get(idxB);
2189
+ if (matchedIdxA !== void 0) {
2190
+ const rowA = rowsA[matchedIdxA];
2191
+ for (let checkIdx = 0; checkIdx < matchedIdxA; checkIdx++) {
2192
+ if (deletedIndices.has(checkIdx) && !processedDeletions.has(checkIdx)) {
2193
+ const deletedRow = rowsA[checkIdx];
2194
+ const changeId = uuid.v4();
2195
+ mergedRows.push(markAllTextAsDeleted(cloneNode2(deletedRow), changeId, author));
2196
+ tableInfos.push({
2197
+ id: changeId,
2198
+ type: "rowDelete",
2199
+ nodeType: "tableRow",
2200
+ location: `Table ${tableIndex}, Row ${checkIdx + 1}`,
2201
+ preview: extractTextPreview(deletedRow),
2202
+ author,
2203
+ date: (/* @__PURE__ */ new Date()).toISOString()
2204
+ });
2205
+ processedDeletions.add(checkIdx);
2206
+ }
2115
2207
  }
2208
+ const { mergedNode, changes } = mergeMatchedBlock(rowA, rowB, idxB, author);
2209
+ mergedRows.push(mergedNode);
2210
+ changeCount += changes;
2211
+ } else {
2212
+ const changeId = uuid.v4();
2213
+ mergedRows.push(markAllTextAsInserted(cloneNode2(rowB), changeId, author));
2214
+ tableInfos.push({
2215
+ id: changeId,
2216
+ type: "rowInsert",
2217
+ nodeType: "tableRow",
2218
+ location: `Table ${tableIndex}, Row ${idxB + 1}`,
2219
+ preview: extractTextPreview(rowB),
2220
+ author,
2221
+ date: (/* @__PURE__ */ new Date()).toISOString()
2222
+ });
2116
2223
  }
2117
2224
  }
2118
- const imageChanges = diffImages(docA, docB);
2119
- for (const imgInsert of imageChanges.inserted) {
2120
- const sharedId = imgInsert.id;
2121
- const date = (/* @__PURE__ */ new Date()).toISOString();
2122
- infos.push({
2123
- id: sharedId,
2124
- type: "imageInsert",
2125
- nodeType: "image",
2126
- location: getImageLocation(imgInsert.path),
2127
- preview: getImagePreview(imgInsert.node),
2128
- author,
2129
- date
2130
- });
2131
- changes.push(imgInsert);
2132
- }
2133
- for (const imgDelete of imageChanges.deleted) {
2134
- const sharedId = imgDelete.id;
2135
- const date = (/* @__PURE__ */ new Date()).toISOString();
2136
- infos.push({
2137
- id: sharedId,
2138
- type: "imageDelete",
2139
- nodeType: "image",
2140
- location: getImageLocation(imgDelete.path),
2141
- preview: getImagePreview(imgDelete.node),
2142
- author,
2143
- date
2144
- });
2145
- changes.push(imgDelete);
2225
+ for (const deletion of rowAlignment.deletions) {
2226
+ const idxA = deletion.path[deletion.path.length - 1];
2227
+ if (!processedDeletions.has(idxA)) {
2228
+ const changeId = uuid.v4();
2229
+ mergedRows.push(markAllTextAsDeleted(cloneNode2(deletion.node), changeId, author));
2230
+ tableInfos.push({
2231
+ id: changeId,
2232
+ type: "rowDelete",
2233
+ nodeType: "tableRow",
2234
+ location: `Table ${tableIndex}, Row ${idxA + 1}`,
2235
+ preview: extractTextPreview(deletion.node),
2236
+ author,
2237
+ date: (/* @__PURE__ */ new Date()).toISOString()
2238
+ });
2239
+ }
2146
2240
  }
2147
- return { changes, infos };
2148
- }
2149
- function generateStructuralChangeSummary(infos) {
2150
- const summary = [];
2151
- const rowInserts = infos.filter((i) => i.type === "rowInsert").length;
2152
- const rowDeletes = infos.filter((i) => i.type === "rowDelete").length;
2153
- const paragraphInserts = infos.filter((i) => i.type === "paragraphInsert").length;
2154
- const paragraphDeletes = infos.filter((i) => i.type === "paragraphDelete").length;
2155
- const listItemInserts = infos.filter((i) => i.type === "listItemInsert").length;
2156
- const listItemDeletes = infos.filter((i) => i.type === "listItemDelete").length;
2157
- const imageInserts = infos.filter((i) => i.type === "imageInsert").length;
2158
- const imageDeletes = infos.filter((i) => i.type === "imageDelete").length;
2159
- if (rowInserts > 0) summary.push(`${rowInserts} row(s) inserted`);
2160
- if (rowDeletes > 0) summary.push(`${rowDeletes} row(s) deleted`);
2161
- if (paragraphInserts > 0) summary.push(`${paragraphInserts} paragraph(s) inserted`);
2162
- if (paragraphDeletes > 0) summary.push(`${paragraphDeletes} paragraph(s) deleted`);
2163
- if (listItemInserts > 0) summary.push(`${listItemInserts} list item(s) inserted`);
2164
- if (listItemDeletes > 0) summary.push(`${listItemDeletes} list item(s) deleted`);
2165
- if (imageInserts > 0) summary.push(`${imageInserts} image(s) inserted`);
2166
- if (imageDeletes > 0) summary.push(`${imageDeletes} image(s) deleted`);
2167
- return summary;
2241
+ const mergedTable = {
2242
+ ...tableB,
2243
+ content: mergedRows
2244
+ };
2245
+ return { mergedTable, tableInfos, changeCount };
2246
+ }
2247
+ function mergeMatchedList(listA, listB, listIndex, author) {
2248
+ const listInfos = [];
2249
+ let changeCount = 0;
2250
+ const itemAlignment = alignListItems(listA, listB, [listIndex - 1], [listIndex - 1]);
2251
+ const mergedItems = [];
2252
+ const matchedFromA = /* @__PURE__ */ new Map();
2253
+ const matchedFromB = /* @__PURE__ */ new Map();
2254
+ for (const match of itemAlignment.matched) {
2255
+ const idxA = match.pathA[match.pathA.length - 1];
2256
+ const idxB = match.pathB[match.pathB.length - 1];
2257
+ matchedFromA.set(idxA, idxB);
2258
+ matchedFromB.set(idxB, idxA);
2259
+ }
2260
+ const deletedIndices = new Set(itemAlignment.deletions.map((d) => d.path[d.path.length - 1]));
2261
+ const processedDeletions = /* @__PURE__ */ new Set();
2262
+ const itemsA = listA.content || [];
2263
+ const itemsB = listB.content || [];
2264
+ for (let idxB = 0; idxB < itemsB.length; idxB++) {
2265
+ const itemB = itemsB[idxB];
2266
+ const matchedIdxA = matchedFromB.get(idxB);
2267
+ if (matchedIdxA !== void 0) {
2268
+ const itemA = itemsA[matchedIdxA];
2269
+ for (let checkIdx = 0; checkIdx < matchedIdxA; checkIdx++) {
2270
+ if (deletedIndices.has(checkIdx) && !processedDeletions.has(checkIdx)) {
2271
+ const deletedItem = itemsA[checkIdx];
2272
+ const changeId = uuid.v4();
2273
+ mergedItems.push(markAllTextAsDeleted(cloneNode2(deletedItem), changeId, author));
2274
+ listInfos.push({
2275
+ id: changeId,
2276
+ type: "listItemDelete",
2277
+ nodeType: "listItem",
2278
+ location: `List ${listIndex}, Item ${checkIdx + 1}`,
2279
+ preview: extractTextPreview(deletedItem),
2280
+ author,
2281
+ date: (/* @__PURE__ */ new Date()).toISOString()
2282
+ });
2283
+ processedDeletions.add(checkIdx);
2284
+ }
2285
+ }
2286
+ const { mergedNode, changes } = mergeMatchedBlock(itemA, itemB, idxB, author);
2287
+ mergedItems.push(mergedNode);
2288
+ changeCount += changes;
2289
+ } else {
2290
+ const changeId = uuid.v4();
2291
+ mergedItems.push(markAllTextAsInserted(cloneNode2(itemB), changeId, author));
2292
+ listInfos.push({
2293
+ id: changeId,
2294
+ type: "listItemInsert",
2295
+ nodeType: "listItem",
2296
+ location: `List ${listIndex}, Item ${idxB + 1}`,
2297
+ preview: extractTextPreview(itemB),
2298
+ author,
2299
+ date: (/* @__PURE__ */ new Date()).toISOString()
2300
+ });
2301
+ }
2302
+ }
2303
+ for (const deletion of itemAlignment.deletions) {
2304
+ const idxA = deletion.path[deletion.path.length - 1];
2305
+ if (!processedDeletions.has(idxA)) {
2306
+ const changeId = uuid.v4();
2307
+ mergedItems.push(markAllTextAsDeleted(cloneNode2(deletion.node), changeId, author));
2308
+ listInfos.push({
2309
+ id: changeId,
2310
+ type: "listItemDelete",
2311
+ nodeType: "listItem",
2312
+ location: `List ${listIndex}, Item ${idxA + 1}`,
2313
+ preview: extractTextPreview(deletion.node),
2314
+ author,
2315
+ date: (/* @__PURE__ */ new Date()).toISOString()
2316
+ });
2317
+ }
2318
+ }
2319
+ const mergedList = {
2320
+ ...listB,
2321
+ content: mergedItems
2322
+ };
2323
+ return { mergedList, listInfos, changeCount };
2324
+ }
2325
+ function createInsertedBlock(node, blockIndex, author) {
2326
+ const changeId = uuid.v4();
2327
+ const markedNode = markAllTextAsInserted(cloneNode2(node), changeId, author);
2328
+ const nodeDesc = getNodeTypeDescription(node);
2329
+ const info = {
2330
+ id: changeId,
2331
+ type: isTable(node) ? "rowInsert" : isList(node) ? "listItemInsert" : isImage(node) ? "imageInsert" : "paragraphInsert",
2332
+ nodeType: node.type || "unknown",
2333
+ location: `${nodeDesc} inserted at position ${blockIndex}`,
2334
+ preview: extractTextPreview(node),
2335
+ author,
2336
+ date: (/* @__PURE__ */ new Date()).toISOString()
2337
+ };
2338
+ return { markedNode, info };
2339
+ }
2340
+ function createDeletedBlock(node, blockIndex, author) {
2341
+ const changeId = uuid.v4();
2342
+ const markedNode = markAllTextAsDeleted(cloneNode2(node), changeId, author);
2343
+ const nodeDesc = getNodeTypeDescription(node);
2344
+ const info = {
2345
+ id: changeId,
2346
+ type: isTable(node) ? "rowDelete" : isList(node) ? "listItemDelete" : isImage(node) ? "imageDelete" : "paragraphDelete",
2347
+ nodeType: node.type || "unknown",
2348
+ location: `${nodeDesc} deleted from position ${blockIndex}`,
2349
+ preview: extractTextPreview(node),
2350
+ author,
2351
+ date: (/* @__PURE__ */ new Date()).toISOString()
2352
+ };
2353
+ return { markedNode, info };
2168
2354
  }
2169
2355
  var permissionResolver = ({ permission }) => {
2170
2356
  return TRACK_CHANGE_PERMISSIONS.includes(permission) ? true : void 0;
@@ -2603,10 +2789,16 @@ var DocxDiffEditor = react.forwardRef(
2603
2789
  }
2604
2790
  newJson = content;
2605
2791
  }
2792
+ const structuralResult = mergeWithStructuralAwareness(
2793
+ sourceJson,
2794
+ newJson,
2795
+ author
2796
+ );
2797
+ const merged = structuralResult.mergedDoc;
2798
+ const structInfos = structuralResult.structuralInfos;
2799
+ setMergedJson(merged);
2606
2800
  const diff = diffDocuments(sourceJson, newJson);
2607
2801
  setDiffResult(diff);
2608
- const merged = mergeDocuments(sourceJson, newJson, diff, author);
2609
- setMergedJson(merged);
2610
2802
  if (superdocRef.current?.activeEditor) {
2611
2803
  setEditorContent(superdocRef.current.activeEditor, merged);
2612
2804
  enableReviewMode(superdocRef.current);
@@ -2627,19 +2819,16 @@ var DocxDiffEditor = react.forwardRef(
2627
2819
  }, 50);
2628
2820
  }
2629
2821
  }
2630
- const { changes: structChanges, infos: structInfos } = processStructuralChanges(
2631
- sourceJson,
2632
- newJson,
2633
- author
2634
- );
2635
2822
  setStructuralChanges(structInfos);
2636
2823
  setIsPaneDismissed(false);
2637
2824
  const insertions = diff.segments.filter((s) => s.type === "insert").length;
2638
2825
  const deletions = diff.segments.filter((s) => s.type === "delete").length;
2639
2826
  const formatChanges = diff.formatChanges?.length || 0;
2640
2827
  const structuralChangeCount = structInfos.length;
2641
- const structuralSummary = generateStructuralChangeSummary(structInfos);
2642
- const combinedSummary = [...diff.summary, ...structuralSummary];
2828
+ const combinedSummary = [...structuralResult.summary];
2829
+ if (diff.summary.length > 0 && structuralResult.summary.length === 0) {
2830
+ combinedSummary.push(...diff.summary);
2831
+ }
2643
2832
  const result = {
2644
2833
  totalChanges: insertions + deletions + formatChanges + structuralChangeCount,
2645
2834
  insertions,
@@ -3033,6 +3222,254 @@ var DocxDiffEditor_default = DocxDiffEditor;
3033
3222
 
3034
3223
  // src/services/index.ts
3035
3224
  init_nodeFingerprint();
3225
+ function markAllTextAsInserted2(node, sharedId, author) {
3226
+ if (node.type === "text") {
3227
+ return {
3228
+ ...node,
3229
+ marks: [...node.marks || [], createTrackInsertMark(author, sharedId)]
3230
+ };
3231
+ }
3232
+ if (node.content && Array.isArray(node.content)) {
3233
+ return {
3234
+ ...node,
3235
+ content: node.content.map(
3236
+ (child) => markAllTextAsInserted2(child, sharedId, author)
3237
+ )
3238
+ };
3239
+ }
3240
+ return node;
3241
+ }
3242
+ function markAllTextAsDeleted2(node, sharedId, author) {
3243
+ if (node.type === "text") {
3244
+ return {
3245
+ ...node,
3246
+ marks: [...node.marks || [], createTrackDeleteMark(author, sharedId)]
3247
+ };
3248
+ }
3249
+ if (node.content && Array.isArray(node.content)) {
3250
+ return {
3251
+ ...node,
3252
+ content: node.content.map(
3253
+ (child) => markAllTextAsDeleted2(child, sharedId, author)
3254
+ )
3255
+ };
3256
+ }
3257
+ return node;
3258
+ }
3259
+ function cloneNode3(node) {
3260
+ return JSON.parse(JSON.stringify(node));
3261
+ }
3262
+ function extractTextPreview2(node, maxLength = 50) {
3263
+ const texts = [];
3264
+ function extract(n) {
3265
+ if (n.type === "text") {
3266
+ texts.push(n.text || "");
3267
+ }
3268
+ if (n.content) {
3269
+ for (const child of n.content) {
3270
+ extract(child);
3271
+ }
3272
+ }
3273
+ }
3274
+ extract(node);
3275
+ const text = texts.join("").trim();
3276
+ if (text.length > maxLength) {
3277
+ return text.substring(0, maxLength - 3) + "...";
3278
+ }
3279
+ return text || "(empty)";
3280
+ }
3281
+ function processStructuralChanges(docA, docB, author = DEFAULT_AUTHOR) {
3282
+ const changes = [];
3283
+ const infos = [];
3284
+ const alignment = alignDocuments(docA, docB);
3285
+ let tableIndex = 0;
3286
+ let listIndex = 0;
3287
+ let paragraphIndex = 0;
3288
+ for (const inserted of alignment.insertions) {
3289
+ const node = inserted.node;
3290
+ const sharedId = uuid.v4();
3291
+ const date = (/* @__PURE__ */ new Date()).toISOString();
3292
+ let type = "paragraphInsert";
3293
+ let location = "";
3294
+ let preview = "";
3295
+ if (isTable(node)) {
3296
+ type = "rowInsert";
3297
+ location = `New table at position ${inserted.path[0] + 1}`;
3298
+ preview = `Table with ${node.content?.length || 0} rows`;
3299
+ tableIndex++;
3300
+ } else if (isList(node)) {
3301
+ type = "listItemInsert";
3302
+ location = `New list at position ${inserted.path[0] + 1}`;
3303
+ preview = `List with ${node.content?.length || 0} items`;
3304
+ listIndex++;
3305
+ } else {
3306
+ type = "paragraphInsert";
3307
+ paragraphIndex++;
3308
+ location = `Paragraph ${paragraphIndex}`;
3309
+ preview = extractTextPreview2(node);
3310
+ }
3311
+ changes.push({
3312
+ id: sharedId,
3313
+ type,
3314
+ nodeType: node.type,
3315
+ path: inserted.path,
3316
+ node: markAllTextAsInserted2(cloneNode3(node), sharedId, author)
3317
+ });
3318
+ infos.push({
3319
+ id: sharedId,
3320
+ type,
3321
+ nodeType: node.type,
3322
+ location,
3323
+ preview,
3324
+ author,
3325
+ date
3326
+ });
3327
+ }
3328
+ for (const deleted of alignment.deletions) {
3329
+ const node = deleted.node;
3330
+ const sharedId = uuid.v4();
3331
+ const date = (/* @__PURE__ */ new Date()).toISOString();
3332
+ let type = "paragraphDelete";
3333
+ let location = "";
3334
+ let preview = "";
3335
+ if (isTable(node)) {
3336
+ type = "rowDelete";
3337
+ location = `Deleted table at position ${deleted.path[0] + 1}`;
3338
+ preview = `Table with ${node.content?.length || 0} rows`;
3339
+ } else if (isList(node)) {
3340
+ type = "listItemDelete";
3341
+ location = `Deleted list at position ${deleted.path[0] + 1}`;
3342
+ preview = `List with ${node.content?.length || 0} items`;
3343
+ } else {
3344
+ type = "paragraphDelete";
3345
+ location = `Deleted paragraph`;
3346
+ preview = extractTextPreview2(node);
3347
+ }
3348
+ changes.push({
3349
+ id: sharedId,
3350
+ type,
3351
+ nodeType: node.type,
3352
+ path: deleted.path,
3353
+ node: markAllTextAsDeleted2(cloneNode3(node), sharedId, author)
3354
+ });
3355
+ infos.push({
3356
+ id: sharedId,
3357
+ type,
3358
+ nodeType: node.type,
3359
+ location,
3360
+ preview,
3361
+ author,
3362
+ date
3363
+ });
3364
+ }
3365
+ for (const match of alignment.matched) {
3366
+ const nodeA = docA.content?.[match.pathA[0]];
3367
+ const nodeB = docB.content?.[match.pathB[0]];
3368
+ if (!nodeA || !nodeB) continue;
3369
+ if (isTable(nodeA) && isTable(nodeB)) {
3370
+ tableIndex++;
3371
+ const tableResult = diffTables(nodeA, nodeB, match.pathA, match.pathB);
3372
+ for (const rowChange of tableResult.rowChanges) {
3373
+ const sharedId = rowChange.id;
3374
+ const date = (/* @__PURE__ */ new Date()).toISOString();
3375
+ const rowIndex = rowChange.path[rowChange.path.length - 1];
3376
+ const isInsert = rowChange.type === "rowInsert";
3377
+ const location = getRowLocation(rowChange.path, rowIndex, tableIndex - 1);
3378
+ const preview = getRowPreview(rowChange.node);
3379
+ const markedNode = isInsert ? markAllTextAsInserted2(cloneNode3(rowChange.node), sharedId, author) : markAllTextAsDeleted2(cloneNode3(rowChange.node), sharedId, author);
3380
+ changes.push({
3381
+ ...rowChange,
3382
+ node: markedNode
3383
+ });
3384
+ infos.push({
3385
+ id: sharedId,
3386
+ type: rowChange.type,
3387
+ nodeType: "tableRow",
3388
+ location,
3389
+ preview,
3390
+ author,
3391
+ date
3392
+ });
3393
+ }
3394
+ }
3395
+ if (isList(nodeA) && isList(nodeB)) {
3396
+ listIndex++;
3397
+ const listResult = diffLists(nodeA, nodeB, match.pathA, match.pathB);
3398
+ for (const itemChange of listResult.itemChanges) {
3399
+ const sharedId = itemChange.id;
3400
+ const date = (/* @__PURE__ */ new Date()).toISOString();
3401
+ const itemIndex = itemChange.path[itemChange.path.length - 1];
3402
+ const isInsert = itemChange.type === "listItemInsert";
3403
+ const location = getListItemLocation(itemChange.path, itemIndex, listIndex - 1);
3404
+ const preview = getListItemPreview(itemChange.node);
3405
+ const markedNode = isInsert ? markAllTextAsInserted2(cloneNode3(itemChange.node), sharedId, author) : markAllTextAsDeleted2(cloneNode3(itemChange.node), sharedId, author);
3406
+ changes.push({
3407
+ ...itemChange,
3408
+ node: markedNode
3409
+ });
3410
+ infos.push({
3411
+ id: sharedId,
3412
+ type: itemChange.type,
3413
+ nodeType: "listItem",
3414
+ location,
3415
+ preview,
3416
+ author,
3417
+ date
3418
+ });
3419
+ }
3420
+ }
3421
+ }
3422
+ const imageChanges = diffImages(docA, docB);
3423
+ for (const imgInsert of imageChanges.inserted) {
3424
+ const sharedId = imgInsert.id;
3425
+ const date = (/* @__PURE__ */ new Date()).toISOString();
3426
+ infos.push({
3427
+ id: sharedId,
3428
+ type: "imageInsert",
3429
+ nodeType: "image",
3430
+ location: getImageLocation(imgInsert.path),
3431
+ preview: getImagePreview(imgInsert.node),
3432
+ author,
3433
+ date
3434
+ });
3435
+ changes.push(imgInsert);
3436
+ }
3437
+ for (const imgDelete of imageChanges.deleted) {
3438
+ const sharedId = imgDelete.id;
3439
+ const date = (/* @__PURE__ */ new Date()).toISOString();
3440
+ infos.push({
3441
+ id: sharedId,
3442
+ type: "imageDelete",
3443
+ nodeType: "image",
3444
+ location: getImageLocation(imgDelete.path),
3445
+ preview: getImagePreview(imgDelete.node),
3446
+ author,
3447
+ date
3448
+ });
3449
+ changes.push(imgDelete);
3450
+ }
3451
+ return { changes, infos };
3452
+ }
3453
+ function generateStructuralChangeSummary(infos) {
3454
+ const summary = [];
3455
+ const rowInserts = infos.filter((i) => i.type === "rowInsert").length;
3456
+ const rowDeletes = infos.filter((i) => i.type === "rowDelete").length;
3457
+ const paragraphInserts = infos.filter((i) => i.type === "paragraphInsert").length;
3458
+ const paragraphDeletes = infos.filter((i) => i.type === "paragraphDelete").length;
3459
+ const listItemInserts = infos.filter((i) => i.type === "listItemInsert").length;
3460
+ const listItemDeletes = infos.filter((i) => i.type === "listItemDelete").length;
3461
+ const imageInserts = infos.filter((i) => i.type === "imageInsert").length;
3462
+ const imageDeletes = infos.filter((i) => i.type === "imageDelete").length;
3463
+ if (rowInserts > 0) summary.push(`${rowInserts} row(s) inserted`);
3464
+ if (rowDeletes > 0) summary.push(`${rowDeletes} row(s) deleted`);
3465
+ if (paragraphInserts > 0) summary.push(`${paragraphInserts} paragraph(s) inserted`);
3466
+ if (paragraphDeletes > 0) summary.push(`${paragraphDeletes} paragraph(s) deleted`);
3467
+ if (listItemInserts > 0) summary.push(`${listItemInserts} list item(s) inserted`);
3468
+ if (listItemDeletes > 0) summary.push(`${listItemDeletes} list item(s) deleted`);
3469
+ if (imageInserts > 0) summary.push(`${imageInserts} image(s) inserted`);
3470
+ if (imageDeletes > 0) summary.push(`${imageDeletes} image(s) deleted`);
3471
+ return summary;
3472
+ }
3036
3473
 
3037
3474
  // src/blankTemplate.ts
3038
3475
  var BLANK_DOCX_BASE64 = `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`;