docx-diff-editor 1.0.61 → 1.0.62

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.mjs CHANGED
@@ -1776,241 +1776,6 @@ function alignListItems(listA, listB, listPathA, listPathB) {
1776
1776
  }
1777
1777
  return alignNodes(itemsA, itemsB);
1778
1778
  }
1779
- function cloneNode(node) {
1780
- return JSON.parse(JSON.stringify(node));
1781
- }
1782
- function getMarkSpansForRange(spansB, start, end) {
1783
- const result = [];
1784
- for (const span of spansB) {
1785
- if (span.to > start && span.from < end) {
1786
- const overlapStart = Math.max(span.from, start);
1787
- const overlapEnd = Math.min(span.to, end);
1788
- result.push({
1789
- relStart: overlapStart - start,
1790
- relEnd: overlapEnd - start,
1791
- marks: span.marks || []
1792
- });
1793
- }
1794
- }
1795
- return result;
1796
- }
1797
- function createInsertedTextNodes(text, posB, spansB, author, replacementId) {
1798
- const result = [];
1799
- const trackMark = createTrackInsertMark(author, replacementId);
1800
- if (posB === void 0 || spansB.length === 0) {
1801
- return [{
1802
- type: "text",
1803
- text,
1804
- marks: [trackMark]
1805
- }];
1806
- }
1807
- const markSpans = getMarkSpansForRange(spansB, posB, posB + text.length);
1808
- if (markSpans.length === 0) {
1809
- return [{
1810
- type: "text",
1811
- text,
1812
- marks: [trackMark]
1813
- }];
1814
- }
1815
- markSpans.sort((a, b) => a.relStart - b.relStart);
1816
- let processedUpTo = 0;
1817
- for (const span of markSpans) {
1818
- if (span.relStart > processedUpTo) {
1819
- result.push({
1820
- type: "text",
1821
- text: text.substring(processedUpTo, span.relStart),
1822
- marks: [trackMark]
1823
- });
1824
- }
1825
- if (span.relEnd > span.relStart) {
1826
- const spanText = text.substring(span.relStart, span.relEnd);
1827
- const normalizedSpanMarks = normalizeMarksForRendering(span.marks);
1828
- const marks = [...normalizedSpanMarks, trackMark];
1829
- result.push({
1830
- type: "text",
1831
- text: spanText,
1832
- marks
1833
- });
1834
- processedUpTo = span.relEnd;
1835
- }
1836
- }
1837
- if (processedUpTo < text.length) {
1838
- result.push({
1839
- type: "text",
1840
- text: text.substring(processedUpTo),
1841
- marks: [trackMark]
1842
- });
1843
- }
1844
- return result;
1845
- }
1846
- function mergeDocuments(docA, docB, diffResult, author = DEFAULT_AUTHOR) {
1847
- const merged = cloneNode(docA);
1848
- const charStates = [];
1849
- let insertions = [];
1850
- const formatChanges = diffResult.formatChanges || [];
1851
- function getFormatChangeAt(pos) {
1852
- for (const fc of formatChanges) {
1853
- if (pos >= fc.from && pos < fc.to) {
1854
- return fc;
1855
- }
1856
- }
1857
- return null;
1858
- }
1859
- let docAOffset = 0;
1860
- const segments = diffResult.segments;
1861
- for (let segIdx = 0; segIdx < segments.length; segIdx++) {
1862
- const segment = segments[segIdx];
1863
- if (segment.type === "equal") {
1864
- for (let i = 0; i < segment.text.length; i++) {
1865
- charStates[docAOffset + i] = { type: "equal" };
1866
- }
1867
- docAOffset += segment.text.length;
1868
- } else if (segment.type === "delete") {
1869
- const nextSegment = segments[segIdx + 1];
1870
- const isReplacement = nextSegment && nextSegment.type === "insert";
1871
- const replacementId = isReplacement ? v4() : void 0;
1872
- for (let i = 0; i < segment.text.length; i++) {
1873
- charStates[docAOffset + i] = { type: "delete", replacementId };
1874
- }
1875
- docAOffset += segment.text.length;
1876
- if (isReplacement && nextSegment) {
1877
- insertions.push({
1878
- afterOffset: docAOffset,
1879
- text: nextSegment.text,
1880
- replacementId,
1881
- posB: nextSegment.posB
1882
- // Capture docB position for mark lookup
1883
- });
1884
- segIdx++;
1885
- }
1886
- } else if (segment.type === "insert") {
1887
- insertions.push({
1888
- afterOffset: docAOffset,
1889
- text: segment.text,
1890
- posB: segment.posB
1891
- // Capture docB position for mark lookup
1892
- });
1893
- }
1894
- }
1895
- const spansB = diffResult.spansB || [];
1896
- function transformNode(node, nodeOffset, path) {
1897
- if (node.type === "text" && node.text) {
1898
- const text = node.text;
1899
- const result = [];
1900
- let i = 0;
1901
- while (i < text.length) {
1902
- const charOffset = nodeOffset + i;
1903
- const charState = charStates[charOffset] || { type: "equal" };
1904
- const insertionsHere = insertions.filter((ins) => ins.afterOffset === charOffset);
1905
- for (const ins of insertionsHere) {
1906
- const insertedNodes = createInsertedTextNodes(
1907
- ins.text,
1908
- ins.posB,
1909
- spansB,
1910
- author,
1911
- ins.replacementId
1912
- );
1913
- result.push(...insertedNodes);
1914
- }
1915
- const currentFormatChange = getFormatChangeAt(nodeOffset + i);
1916
- let j = i + 1;
1917
- while (j < text.length) {
1918
- const nextState = charStates[nodeOffset + j] || { type: "equal" };
1919
- if (nextState.type !== charState.type) break;
1920
- if (insertions.some((ins) => ins.afterOffset === nodeOffset + j)) break;
1921
- const nextFormatChange = getFormatChangeAt(nodeOffset + j);
1922
- if (currentFormatChange !== nextFormatChange) break;
1923
- j++;
1924
- }
1925
- const chunk = text.substring(i, j);
1926
- let marks = [...node.marks || []];
1927
- if (charState.type === "delete") {
1928
- marks.push(createTrackDeleteMark(author, charState.replacementId));
1929
- } else if (charState.type === "equal") {
1930
- if (currentFormatChange) {
1931
- const trackFormatMark = createTrackFormatMark(
1932
- currentFormatChange.before,
1933
- currentFormatChange.after,
1934
- author
1935
- );
1936
- const normalizedAfterMarks = normalizeMarksForRendering(currentFormatChange.after);
1937
- marks = [...normalizedAfterMarks, trackFormatMark];
1938
- }
1939
- }
1940
- result.push({
1941
- type: "text",
1942
- text: chunk,
1943
- marks: marks.length > 0 ? marks : void 0
1944
- });
1945
- i = j;
1946
- }
1947
- const endOffset = nodeOffset + text.length;
1948
- const endInsertions = insertions.filter((ins) => ins.afterOffset === endOffset);
1949
- for (const ins of endInsertions) {
1950
- const insertedNodes = createInsertedTextNodes(
1951
- ins.text,
1952
- ins.posB,
1953
- spansB,
1954
- author,
1955
- ins.replacementId
1956
- );
1957
- result.push(...insertedNodes);
1958
- }
1959
- insertions = insertions.filter(
1960
- (ins) => ins.afterOffset < nodeOffset || ins.afterOffset > endOffset
1961
- );
1962
- return { nodes: result, consumedLength: text.length };
1963
- }
1964
- if (node.content && Array.isArray(node.content)) {
1965
- const newContent = [];
1966
- let offset = nodeOffset;
1967
- for (const child of node.content) {
1968
- const { nodes, consumedLength } = transformNode(child, offset);
1969
- newContent.push(...nodes);
1970
- offset += consumedLength;
1971
- }
1972
- return {
1973
- nodes: [{ ...node, content: newContent }],
1974
- consumedLength: offset - nodeOffset
1975
- };
1976
- }
1977
- return { nodes: [node], consumedLength: 0 };
1978
- }
1979
- if (merged.content && Array.isArray(merged.content)) {
1980
- const newContent = [];
1981
- let offset = 0;
1982
- for (let i = 0; i < merged.content.length; i++) {
1983
- const child = merged.content[i];
1984
- const { nodes, consumedLength } = transformNode(child, offset);
1985
- newContent.push(...nodes);
1986
- offset += consumedLength;
1987
- }
1988
- merged.content = newContent;
1989
- }
1990
- if (insertions.length > 0) {
1991
- for (const ins of insertions) {
1992
- const insertedNodes = createInsertedTextNodes(
1993
- ins.text,
1994
- ins.posB,
1995
- spansB,
1996
- author,
1997
- ins.replacementId
1998
- );
1999
- const insertNode = {
2000
- type: "paragraph",
2001
- content: [
2002
- {
2003
- type: "run",
2004
- content: insertedNodes
2005
- }
2006
- ]
2007
- };
2008
- if (!merged.content) merged.content = [];
2009
- merged.content.push(insertNode);
2010
- }
2011
- }
2012
- return merged;
2013
- }
2014
1779
 
2015
1780
  // src/services/attrComparer.ts
2016
1781
  var KNOWN_DEFAULTS = {
@@ -2517,10 +2282,7 @@ function getImagePreview(node) {
2517
2282
  return "(image)";
2518
2283
  }
2519
2284
 
2520
- // src/services/structuralMerger.ts
2521
- function cloneNode2(node) {
2522
- return JSON.parse(JSON.stringify(node));
2523
- }
2285
+ // src/services/blockLevelMerger.ts
2524
2286
  function markAllTextAsInserted(node, sharedId, author) {
2525
2287
  if (node.type === "text") {
2526
2288
  const existingMarks = normalizeMarksForRendering(node.marks || []);
@@ -2537,7 +2299,7 @@ function markAllTextAsInserted(node, sharedId, author) {
2537
2299
  )
2538
2300
  };
2539
2301
  }
2540
- return { ...node };
2302
+ return node;
2541
2303
  }
2542
2304
  function markAllTextAsDeleted(node, sharedId, author) {
2543
2305
  if (node.type === "text") {
@@ -2555,7 +2317,10 @@ function markAllTextAsDeleted(node, sharedId, author) {
2555
2317
  )
2556
2318
  };
2557
2319
  }
2558
- return { ...node };
2320
+ return node;
2321
+ }
2322
+ function cloneNode(node) {
2323
+ return JSON.parse(JSON.stringify(node));
2559
2324
  }
2560
2325
  function extractTextPreview(node, maxLength = 50) {
2561
2326
  const texts = [];
@@ -2576,17 +2341,504 @@ function extractTextPreview(node, maxLength = 50) {
2576
2341
  }
2577
2342
  return text || "(empty)";
2578
2343
  }
2579
- function getNodeTypeDescription(node) {
2580
- if (isTable(node)) return "Table";
2581
- if (isList(node)) return "List";
2582
- if (isListItem(node)) return "List item";
2583
- if (isTableRow(node)) return "Table row";
2584
- if (isImage(node)) return "Image";
2585
- if (node.type === "heading") return `Heading ${node.attrs?.level || 1}`;
2586
- if (node.type === "paragraph") return "Paragraph";
2587
- if (node.type === "blockquote") return "Blockquote";
2588
- if (node.type === "codeBlock") return "Code block";
2589
- return node.type || "Block";
2344
+ function processStructuralChanges(docA, docB, author = DEFAULT_AUTHOR) {
2345
+ const changes = [];
2346
+ const infos = [];
2347
+ const alignment = alignDocuments(docA, docB);
2348
+ let tableIndex = 0;
2349
+ let listIndex = 0;
2350
+ let paragraphIndex = 0;
2351
+ for (const inserted of alignment.insertions) {
2352
+ const node = inserted.node;
2353
+ const sharedId = v4();
2354
+ const date = (/* @__PURE__ */ new Date()).toISOString();
2355
+ let type = "paragraphInsert";
2356
+ let location = "";
2357
+ let preview = "";
2358
+ if (isTable(node)) {
2359
+ type = "rowInsert";
2360
+ location = `New table at position ${inserted.path[0] + 1}`;
2361
+ preview = `Table with ${node.content?.length || 0} rows`;
2362
+ tableIndex++;
2363
+ } else if (isList(node)) {
2364
+ type = "listItemInsert";
2365
+ location = `New list at position ${inserted.path[0] + 1}`;
2366
+ preview = `List with ${node.content?.length || 0} items`;
2367
+ listIndex++;
2368
+ } else {
2369
+ type = "paragraphInsert";
2370
+ paragraphIndex++;
2371
+ location = `Paragraph ${paragraphIndex}`;
2372
+ preview = extractTextPreview(node);
2373
+ }
2374
+ changes.push({
2375
+ id: sharedId,
2376
+ type,
2377
+ nodeType: node.type,
2378
+ path: inserted.path,
2379
+ node: markAllTextAsInserted(cloneNode(node), sharedId, author)
2380
+ });
2381
+ infos.push({
2382
+ id: sharedId,
2383
+ type,
2384
+ nodeType: node.type,
2385
+ location,
2386
+ preview,
2387
+ author,
2388
+ date
2389
+ });
2390
+ }
2391
+ for (const deleted of alignment.deletions) {
2392
+ const node = deleted.node;
2393
+ const sharedId = v4();
2394
+ const date = (/* @__PURE__ */ new Date()).toISOString();
2395
+ let type = "paragraphDelete";
2396
+ let location = "";
2397
+ let preview = "";
2398
+ if (isTable(node)) {
2399
+ type = "rowDelete";
2400
+ location = `Deleted table at position ${deleted.path[0] + 1}`;
2401
+ preview = `Table with ${node.content?.length || 0} rows`;
2402
+ } else if (isList(node)) {
2403
+ type = "listItemDelete";
2404
+ location = `Deleted list at position ${deleted.path[0] + 1}`;
2405
+ preview = `List with ${node.content?.length || 0} items`;
2406
+ } else {
2407
+ type = "paragraphDelete";
2408
+ location = `Deleted paragraph`;
2409
+ preview = extractTextPreview(node);
2410
+ }
2411
+ changes.push({
2412
+ id: sharedId,
2413
+ type,
2414
+ nodeType: node.type,
2415
+ path: deleted.path,
2416
+ node: markAllTextAsDeleted(cloneNode(node), sharedId, author)
2417
+ });
2418
+ infos.push({
2419
+ id: sharedId,
2420
+ type,
2421
+ nodeType: node.type,
2422
+ location,
2423
+ preview,
2424
+ author,
2425
+ date
2426
+ });
2427
+ }
2428
+ for (const match of alignment.matched) {
2429
+ const nodeA = docA.content?.[match.pathA[0]];
2430
+ const nodeB = docB.content?.[match.pathB[0]];
2431
+ if (!nodeA || !nodeB) continue;
2432
+ if (isTable(nodeA) && isTable(nodeB)) {
2433
+ tableIndex++;
2434
+ const tableResult = diffTables(nodeA, nodeB, match.pathA, match.pathB);
2435
+ for (const rowChange of tableResult.rowChanges) {
2436
+ const sharedId = rowChange.id;
2437
+ const date = (/* @__PURE__ */ new Date()).toISOString();
2438
+ const rowIndex = rowChange.path[rowChange.path.length - 1];
2439
+ const isInsert = rowChange.type === "rowInsert";
2440
+ const location = getRowLocation(rowChange.path, rowIndex, tableIndex - 1);
2441
+ const preview = getRowPreview(rowChange.node);
2442
+ const markedNode = isInsert ? markAllTextAsInserted(cloneNode(rowChange.node), sharedId, author) : markAllTextAsDeleted(cloneNode(rowChange.node), sharedId, author);
2443
+ changes.push({
2444
+ ...rowChange,
2445
+ node: markedNode
2446
+ });
2447
+ infos.push({
2448
+ id: sharedId,
2449
+ type: rowChange.type,
2450
+ nodeType: "tableRow",
2451
+ location,
2452
+ preview,
2453
+ author,
2454
+ date
2455
+ });
2456
+ }
2457
+ }
2458
+ if (isList(nodeA) && isList(nodeB)) {
2459
+ listIndex++;
2460
+ const listResult = diffLists(nodeA, nodeB, match.pathA, match.pathB);
2461
+ for (const itemChange of listResult.itemChanges) {
2462
+ const sharedId = itemChange.id;
2463
+ const date = (/* @__PURE__ */ new Date()).toISOString();
2464
+ const itemIndex = itemChange.path[itemChange.path.length - 1];
2465
+ const isInsert = itemChange.type === "listItemInsert";
2466
+ const location = getListItemLocation(itemChange.path, itemIndex, listIndex - 1);
2467
+ const preview = getListItemPreview(itemChange.node);
2468
+ const markedNode = isInsert ? markAllTextAsInserted(cloneNode(itemChange.node), sharedId, author) : markAllTextAsDeleted(cloneNode(itemChange.node), sharedId, author);
2469
+ changes.push({
2470
+ ...itemChange,
2471
+ node: markedNode
2472
+ });
2473
+ infos.push({
2474
+ id: sharedId,
2475
+ type: itemChange.type,
2476
+ nodeType: "listItem",
2477
+ location,
2478
+ preview,
2479
+ author,
2480
+ date
2481
+ });
2482
+ }
2483
+ }
2484
+ }
2485
+ const imageChanges = diffImages(docA, docB);
2486
+ for (const imgInsert of imageChanges.inserted) {
2487
+ const sharedId = imgInsert.id;
2488
+ const date = (/* @__PURE__ */ new Date()).toISOString();
2489
+ infos.push({
2490
+ id: sharedId,
2491
+ type: "imageInsert",
2492
+ nodeType: "image",
2493
+ location: getImageLocation(imgInsert.path),
2494
+ preview: getImagePreview(imgInsert.node),
2495
+ author,
2496
+ date
2497
+ });
2498
+ changes.push(imgInsert);
2499
+ }
2500
+ for (const imgDelete of imageChanges.deleted) {
2501
+ const sharedId = imgDelete.id;
2502
+ const date = (/* @__PURE__ */ new Date()).toISOString();
2503
+ infos.push({
2504
+ id: sharedId,
2505
+ type: "imageDelete",
2506
+ nodeType: "image",
2507
+ location: getImageLocation(imgDelete.path),
2508
+ preview: getImagePreview(imgDelete.node),
2509
+ author,
2510
+ date
2511
+ });
2512
+ changes.push(imgDelete);
2513
+ }
2514
+ return { changes, infos };
2515
+ }
2516
+ function generateStructuralChangeSummary(infos) {
2517
+ const summary = [];
2518
+ const rowInserts = infos.filter((i) => i.type === "rowInsert").length;
2519
+ const rowDeletes = infos.filter((i) => i.type === "rowDelete").length;
2520
+ const paragraphInserts = infos.filter((i) => i.type === "paragraphInsert").length;
2521
+ const paragraphDeletes = infos.filter((i) => i.type === "paragraphDelete").length;
2522
+ const listItemInserts = infos.filter((i) => i.type === "listItemInsert").length;
2523
+ const listItemDeletes = infos.filter((i) => i.type === "listItemDelete").length;
2524
+ const imageInserts = infos.filter((i) => i.type === "imageInsert").length;
2525
+ const imageDeletes = infos.filter((i) => i.type === "imageDelete").length;
2526
+ if (rowInserts > 0) summary.push(`${rowInserts} row(s) inserted`);
2527
+ if (rowDeletes > 0) summary.push(`${rowDeletes} row(s) deleted`);
2528
+ if (paragraphInserts > 0) summary.push(`${paragraphInserts} paragraph(s) inserted`);
2529
+ if (paragraphDeletes > 0) summary.push(`${paragraphDeletes} paragraph(s) deleted`);
2530
+ if (listItemInserts > 0) summary.push(`${listItemInserts} list item(s) inserted`);
2531
+ if (listItemDeletes > 0) summary.push(`${listItemDeletes} list item(s) deleted`);
2532
+ if (imageInserts > 0) summary.push(`${imageInserts} image(s) inserted`);
2533
+ if (imageDeletes > 0) summary.push(`${imageDeletes} image(s) deleted`);
2534
+ return summary;
2535
+ }
2536
+ function cloneNode2(node) {
2537
+ return JSON.parse(JSON.stringify(node));
2538
+ }
2539
+ function getMarkSpansForRange(spansB, start, end) {
2540
+ const result = [];
2541
+ for (const span of spansB) {
2542
+ if (span.to > start && span.from < end) {
2543
+ const overlapStart = Math.max(span.from, start);
2544
+ const overlapEnd = Math.min(span.to, end);
2545
+ result.push({
2546
+ relStart: overlapStart - start,
2547
+ relEnd: overlapEnd - start,
2548
+ marks: span.marks || []
2549
+ });
2550
+ }
2551
+ }
2552
+ return result;
2553
+ }
2554
+ function createInsertedTextNodes(text, posB, spansB, author, replacementId) {
2555
+ const result = [];
2556
+ const trackMark = createTrackInsertMark(author, replacementId);
2557
+ if (posB === void 0 || spansB.length === 0) {
2558
+ return [{
2559
+ type: "text",
2560
+ text,
2561
+ marks: [trackMark]
2562
+ }];
2563
+ }
2564
+ const markSpans = getMarkSpansForRange(spansB, posB, posB + text.length);
2565
+ if (markSpans.length === 0) {
2566
+ return [{
2567
+ type: "text",
2568
+ text,
2569
+ marks: [trackMark]
2570
+ }];
2571
+ }
2572
+ markSpans.sort((a, b) => a.relStart - b.relStart);
2573
+ let processedUpTo = 0;
2574
+ for (const span of markSpans) {
2575
+ if (span.relStart > processedUpTo) {
2576
+ result.push({
2577
+ type: "text",
2578
+ text: text.substring(processedUpTo, span.relStart),
2579
+ marks: [trackMark]
2580
+ });
2581
+ }
2582
+ if (span.relEnd > span.relStart) {
2583
+ const spanText = text.substring(span.relStart, span.relEnd);
2584
+ const normalizedSpanMarks = normalizeMarksForRendering(span.marks);
2585
+ const marks = [...normalizedSpanMarks, trackMark];
2586
+ result.push({
2587
+ type: "text",
2588
+ text: spanText,
2589
+ marks
2590
+ });
2591
+ processedUpTo = span.relEnd;
2592
+ }
2593
+ }
2594
+ if (processedUpTo < text.length) {
2595
+ result.push({
2596
+ type: "text",
2597
+ text: text.substring(processedUpTo),
2598
+ marks: [trackMark]
2599
+ });
2600
+ }
2601
+ return result;
2602
+ }
2603
+ function mergeDocuments(docA, docB, diffResult, author = DEFAULT_AUTHOR) {
2604
+ const merged = cloneNode2(docA);
2605
+ const charStates = [];
2606
+ let insertions = [];
2607
+ const formatChanges = diffResult.formatChanges || [];
2608
+ function getFormatChangeAt(pos) {
2609
+ for (const fc of formatChanges) {
2610
+ if (pos >= fc.from && pos < fc.to) {
2611
+ return fc;
2612
+ }
2613
+ }
2614
+ return null;
2615
+ }
2616
+ let docAOffset = 0;
2617
+ const segments = diffResult.segments;
2618
+ for (let segIdx = 0; segIdx < segments.length; segIdx++) {
2619
+ const segment = segments[segIdx];
2620
+ if (segment.type === "equal") {
2621
+ for (let i = 0; i < segment.text.length; i++) {
2622
+ charStates[docAOffset + i] = { type: "equal" };
2623
+ }
2624
+ docAOffset += segment.text.length;
2625
+ } else if (segment.type === "delete") {
2626
+ const nextSegment = segments[segIdx + 1];
2627
+ const isReplacement = nextSegment && nextSegment.type === "insert";
2628
+ const replacementId = isReplacement ? v4() : void 0;
2629
+ for (let i = 0; i < segment.text.length; i++) {
2630
+ charStates[docAOffset + i] = { type: "delete", replacementId };
2631
+ }
2632
+ docAOffset += segment.text.length;
2633
+ if (isReplacement && nextSegment) {
2634
+ insertions.push({
2635
+ afterOffset: docAOffset,
2636
+ text: nextSegment.text,
2637
+ replacementId,
2638
+ posB: nextSegment.posB
2639
+ // Capture docB position for mark lookup
2640
+ });
2641
+ segIdx++;
2642
+ }
2643
+ } else if (segment.type === "insert") {
2644
+ insertions.push({
2645
+ afterOffset: docAOffset,
2646
+ text: segment.text,
2647
+ posB: segment.posB
2648
+ // Capture docB position for mark lookup
2649
+ });
2650
+ }
2651
+ }
2652
+ const spansB = diffResult.spansB || [];
2653
+ function transformNode(node, nodeOffset, path) {
2654
+ if (node.type === "text" && node.text) {
2655
+ const text = node.text;
2656
+ const result = [];
2657
+ let i = 0;
2658
+ while (i < text.length) {
2659
+ const charOffset = nodeOffset + i;
2660
+ const charState = charStates[charOffset] || { type: "equal" };
2661
+ const insertionsHere = insertions.filter((ins) => ins.afterOffset === charOffset);
2662
+ for (const ins of insertionsHere) {
2663
+ const insertedNodes = createInsertedTextNodes(
2664
+ ins.text,
2665
+ ins.posB,
2666
+ spansB,
2667
+ author,
2668
+ ins.replacementId
2669
+ );
2670
+ result.push(...insertedNodes);
2671
+ }
2672
+ const currentFormatChange = getFormatChangeAt(nodeOffset + i);
2673
+ let j = i + 1;
2674
+ while (j < text.length) {
2675
+ const nextState = charStates[nodeOffset + j] || { type: "equal" };
2676
+ if (nextState.type !== charState.type) break;
2677
+ if (insertions.some((ins) => ins.afterOffset === nodeOffset + j)) break;
2678
+ const nextFormatChange = getFormatChangeAt(nodeOffset + j);
2679
+ if (currentFormatChange !== nextFormatChange) break;
2680
+ j++;
2681
+ }
2682
+ const chunk = text.substring(i, j);
2683
+ let marks = [...node.marks || []];
2684
+ if (charState.type === "delete") {
2685
+ marks.push(createTrackDeleteMark(author, charState.replacementId));
2686
+ } else if (charState.type === "equal") {
2687
+ if (currentFormatChange) {
2688
+ const trackFormatMark = createTrackFormatMark(
2689
+ currentFormatChange.before,
2690
+ currentFormatChange.after,
2691
+ author
2692
+ );
2693
+ const normalizedAfterMarks = normalizeMarksForRendering(currentFormatChange.after);
2694
+ marks = [...normalizedAfterMarks, trackFormatMark];
2695
+ }
2696
+ }
2697
+ result.push({
2698
+ type: "text",
2699
+ text: chunk,
2700
+ marks: marks.length > 0 ? marks : void 0
2701
+ });
2702
+ i = j;
2703
+ }
2704
+ const endOffset = nodeOffset + text.length;
2705
+ const endInsertions = insertions.filter((ins) => ins.afterOffset === endOffset);
2706
+ for (const ins of endInsertions) {
2707
+ const insertedNodes = createInsertedTextNodes(
2708
+ ins.text,
2709
+ ins.posB,
2710
+ spansB,
2711
+ author,
2712
+ ins.replacementId
2713
+ );
2714
+ result.push(...insertedNodes);
2715
+ }
2716
+ insertions = insertions.filter(
2717
+ (ins) => ins.afterOffset < nodeOffset || ins.afterOffset > endOffset
2718
+ );
2719
+ return { nodes: result, consumedLength: text.length };
2720
+ }
2721
+ if (node.content && Array.isArray(node.content)) {
2722
+ const newContent = [];
2723
+ let offset = nodeOffset;
2724
+ for (const child of node.content) {
2725
+ const { nodes, consumedLength } = transformNode(child, offset);
2726
+ newContent.push(...nodes);
2727
+ offset += consumedLength;
2728
+ }
2729
+ return {
2730
+ nodes: [{ ...node, content: newContent }],
2731
+ consumedLength: offset - nodeOffset
2732
+ };
2733
+ }
2734
+ return { nodes: [node], consumedLength: 0 };
2735
+ }
2736
+ if (merged.content && Array.isArray(merged.content)) {
2737
+ const newContent = [];
2738
+ let offset = 0;
2739
+ for (let i = 0; i < merged.content.length; i++) {
2740
+ const child = merged.content[i];
2741
+ const { nodes, consumedLength } = transformNode(child, offset);
2742
+ newContent.push(...nodes);
2743
+ offset += consumedLength;
2744
+ }
2745
+ merged.content = newContent;
2746
+ }
2747
+ if (insertions.length > 0) {
2748
+ for (const ins of insertions) {
2749
+ const insertedNodes = createInsertedTextNodes(
2750
+ ins.text,
2751
+ ins.posB,
2752
+ spansB,
2753
+ author,
2754
+ ins.replacementId
2755
+ );
2756
+ const insertNode = {
2757
+ type: "paragraph",
2758
+ content: [
2759
+ {
2760
+ type: "run",
2761
+ content: insertedNodes
2762
+ }
2763
+ ]
2764
+ };
2765
+ if (!merged.content) merged.content = [];
2766
+ merged.content.push(insertNode);
2767
+ }
2768
+ }
2769
+ return merged;
2770
+ }
2771
+
2772
+ // src/services/structuralMerger.ts
2773
+ function cloneNode3(node) {
2774
+ return JSON.parse(JSON.stringify(node));
2775
+ }
2776
+ function markAllTextAsInserted2(node, sharedId, author) {
2777
+ if (node.type === "text") {
2778
+ const existingMarks = normalizeMarksForRendering(node.marks || []);
2779
+ return {
2780
+ ...node,
2781
+ marks: [...existingMarks, createTrackInsertMark(author, sharedId)]
2782
+ };
2783
+ }
2784
+ if (node.content && Array.isArray(node.content)) {
2785
+ return {
2786
+ ...node,
2787
+ content: node.content.map(
2788
+ (child) => markAllTextAsInserted2(child, sharedId, author)
2789
+ )
2790
+ };
2791
+ }
2792
+ return { ...node };
2793
+ }
2794
+ function markAllTextAsDeleted2(node, sharedId, author) {
2795
+ if (node.type === "text") {
2796
+ const existingMarks = normalizeMarksForRendering(node.marks || []);
2797
+ return {
2798
+ ...node,
2799
+ marks: [...existingMarks, createTrackDeleteMark(author, sharedId)]
2800
+ };
2801
+ }
2802
+ if (node.content && Array.isArray(node.content)) {
2803
+ return {
2804
+ ...node,
2805
+ content: node.content.map(
2806
+ (child) => markAllTextAsDeleted2(child, sharedId, author)
2807
+ )
2808
+ };
2809
+ }
2810
+ return { ...node };
2811
+ }
2812
+ function extractTextPreview2(node, maxLength = 50) {
2813
+ const texts = [];
2814
+ function extract(n) {
2815
+ if (n.type === "text") {
2816
+ texts.push(n.text || "");
2817
+ }
2818
+ if (n.content) {
2819
+ for (const child of n.content) {
2820
+ extract(child);
2821
+ }
2822
+ }
2823
+ }
2824
+ extract(node);
2825
+ const text = texts.join("").trim();
2826
+ if (text.length > maxLength) {
2827
+ return text.substring(0, maxLength - 3) + "...";
2828
+ }
2829
+ return text || "(empty)";
2830
+ }
2831
+ function getNodeTypeDescription(node) {
2832
+ if (isTable(node)) return "Table";
2833
+ if (isList(node)) return "List";
2834
+ if (isListItem(node)) return "List item";
2835
+ if (isTableRow(node)) return "Table row";
2836
+ if (isImage(node)) return "Image";
2837
+ if (node.type === "heading") return `Heading ${node.attrs?.level || 1}`;
2838
+ if (node.type === "paragraph") return "Paragraph";
2839
+ if (node.type === "blockquote") return "Blockquote";
2840
+ if (node.type === "codeBlock") return "Code block";
2841
+ return node.type || "Block";
2590
2842
  }
2591
2843
  function mergeWithStructuralAwareness(docA, docB, author = DEFAULT_AUTHOR) {
2592
2844
  const structuralInfos = [];
@@ -2743,7 +2995,7 @@ function mergeMatchedBlock(nodeA, nodeB, blockIndex, author) {
2743
2995
  diff,
2744
2996
  author
2745
2997
  );
2746
- const mergedNode = merged.content?.[0] || cloneNode2(nodeB);
2998
+ const mergedNode = merged.content?.[0] || cloneNode3(nodeB);
2747
2999
  return { mergedNode, infos, changes };
2748
3000
  }
2749
3001
  function mergeMatchedTable(tableA, tableB, tableIndex, author) {
@@ -2772,13 +3024,13 @@ function mergeMatchedTable(tableA, tableB, tableIndex, author) {
2772
3024
  if (deletedIndices.has(checkIdx) && !processedDeletions.has(checkIdx)) {
2773
3025
  const deletedRow = rowsA[checkIdx];
2774
3026
  const changeId = v4();
2775
- mergedRows.push(markAllTextAsDeleted(cloneNode2(deletedRow), changeId, author));
3027
+ mergedRows.push(markAllTextAsDeleted2(cloneNode3(deletedRow), changeId, author));
2776
3028
  tableInfos.push({
2777
3029
  id: changeId,
2778
3030
  type: "rowDelete",
2779
3031
  nodeType: "tableRow",
2780
3032
  location: `Table ${tableIndex}, Row ${checkIdx + 1}`,
2781
- preview: extractTextPreview(deletedRow),
3033
+ preview: extractTextPreview2(deletedRow),
2782
3034
  author,
2783
3035
  date: (/* @__PURE__ */ new Date()).toISOString()
2784
3036
  });
@@ -2790,13 +3042,13 @@ function mergeMatchedTable(tableA, tableB, tableIndex, author) {
2790
3042
  changeCount += changes;
2791
3043
  } else {
2792
3044
  const changeId = v4();
2793
- mergedRows.push(markAllTextAsInserted(cloneNode2(rowB), changeId, author));
3045
+ mergedRows.push(markAllTextAsInserted2(cloneNode3(rowB), changeId, author));
2794
3046
  tableInfos.push({
2795
3047
  id: changeId,
2796
3048
  type: "rowInsert",
2797
3049
  nodeType: "tableRow",
2798
3050
  location: `Table ${tableIndex}, Row ${idxB + 1}`,
2799
- preview: extractTextPreview(rowB),
3051
+ preview: extractTextPreview2(rowB),
2800
3052
  author,
2801
3053
  date: (/* @__PURE__ */ new Date()).toISOString()
2802
3054
  });
@@ -2806,13 +3058,13 @@ function mergeMatchedTable(tableA, tableB, tableIndex, author) {
2806
3058
  const idxA = deletion.path[deletion.path.length - 1];
2807
3059
  if (!processedDeletions.has(idxA)) {
2808
3060
  const changeId = v4();
2809
- mergedRows.push(markAllTextAsDeleted(cloneNode2(deletion.node), changeId, author));
3061
+ mergedRows.push(markAllTextAsDeleted2(cloneNode3(deletion.node), changeId, author));
2810
3062
  tableInfos.push({
2811
3063
  id: changeId,
2812
3064
  type: "rowDelete",
2813
3065
  nodeType: "tableRow",
2814
3066
  location: `Table ${tableIndex}, Row ${idxA + 1}`,
2815
- preview: extractTextPreview(deletion.node),
3067
+ preview: extractTextPreview2(deletion.node),
2816
3068
  author,
2817
3069
  date: (/* @__PURE__ */ new Date()).toISOString()
2818
3070
  });
@@ -2850,13 +3102,13 @@ function mergeMatchedList(listA, listB, listIndex, author) {
2850
3102
  if (deletedIndices.has(checkIdx) && !processedDeletions.has(checkIdx)) {
2851
3103
  const deletedItem = itemsA[checkIdx];
2852
3104
  const changeId = v4();
2853
- mergedItems.push(markAllTextAsDeleted(cloneNode2(deletedItem), changeId, author));
3105
+ mergedItems.push(markAllTextAsDeleted2(cloneNode3(deletedItem), changeId, author));
2854
3106
  listInfos.push({
2855
3107
  id: changeId,
2856
3108
  type: "listItemDelete",
2857
3109
  nodeType: "listItem",
2858
3110
  location: `List ${listIndex}, Item ${checkIdx + 1}`,
2859
- preview: extractTextPreview(deletedItem),
3111
+ preview: extractTextPreview2(deletedItem),
2860
3112
  author,
2861
3113
  date: (/* @__PURE__ */ new Date()).toISOString()
2862
3114
  });
@@ -2868,13 +3120,13 @@ function mergeMatchedList(listA, listB, listIndex, author) {
2868
3120
  changeCount += changes;
2869
3121
  } else {
2870
3122
  const changeId = v4();
2871
- mergedItems.push(markAllTextAsInserted(cloneNode2(itemB), changeId, author));
3123
+ mergedItems.push(markAllTextAsInserted2(cloneNode3(itemB), changeId, author));
2872
3124
  listInfos.push({
2873
3125
  id: changeId,
2874
3126
  type: "listItemInsert",
2875
3127
  nodeType: "listItem",
2876
3128
  location: `List ${listIndex}, Item ${idxB + 1}`,
2877
- preview: extractTextPreview(itemB),
3129
+ preview: extractTextPreview2(itemB),
2878
3130
  author,
2879
3131
  date: (/* @__PURE__ */ new Date()).toISOString()
2880
3132
  });
@@ -2884,13 +3136,13 @@ function mergeMatchedList(listA, listB, listIndex, author) {
2884
3136
  const idxA = deletion.path[deletion.path.length - 1];
2885
3137
  if (!processedDeletions.has(idxA)) {
2886
3138
  const changeId = v4();
2887
- mergedItems.push(markAllTextAsDeleted(cloneNode2(deletion.node), changeId, author));
3139
+ mergedItems.push(markAllTextAsDeleted2(cloneNode3(deletion.node), changeId, author));
2888
3140
  listInfos.push({
2889
3141
  id: changeId,
2890
3142
  type: "listItemDelete",
2891
3143
  nodeType: "listItem",
2892
3144
  location: `List ${listIndex}, Item ${idxA + 1}`,
2893
- preview: extractTextPreview(deletion.node),
3145
+ preview: extractTextPreview2(deletion.node),
2894
3146
  author,
2895
3147
  date: (/* @__PURE__ */ new Date()).toISOString()
2896
3148
  });
@@ -2904,14 +3156,14 @@ function mergeMatchedList(listA, listB, listIndex, author) {
2904
3156
  }
2905
3157
  function createInsertedBlock(node, blockIndex, author) {
2906
3158
  const changeId = v4();
2907
- const markedNode = markAllTextAsInserted(cloneNode2(node), changeId, author);
3159
+ const markedNode = markAllTextAsInserted2(cloneNode3(node), changeId, author);
2908
3160
  const nodeDesc = getNodeTypeDescription(node);
2909
3161
  const info = {
2910
3162
  id: changeId,
2911
3163
  type: isTable(node) ? "rowInsert" : isList(node) ? "listItemInsert" : isImage(node) ? "imageInsert" : "paragraphInsert",
2912
3164
  nodeType: node.type || "unknown",
2913
3165
  location: `${nodeDesc} inserted at position ${blockIndex}`,
2914
- preview: extractTextPreview(node),
3166
+ preview: extractTextPreview2(node),
2915
3167
  author,
2916
3168
  date: (/* @__PURE__ */ new Date()).toISOString()
2917
3169
  };
@@ -2919,14 +3171,14 @@ function createInsertedBlock(node, blockIndex, author) {
2919
3171
  }
2920
3172
  function createDeletedBlock(node, blockIndex, author) {
2921
3173
  const changeId = v4();
2922
- const markedNode = markAllTextAsDeleted(cloneNode2(node), changeId, author);
3174
+ const markedNode = markAllTextAsDeleted2(cloneNode3(node), changeId, author);
2923
3175
  const nodeDesc = getNodeTypeDescription(node);
2924
3176
  const info = {
2925
3177
  id: changeId,
2926
3178
  type: isTable(node) ? "rowDelete" : isList(node) ? "listItemDelete" : isImage(node) ? "imageDelete" : "paragraphDelete",
2927
3179
  nodeType: node.type || "unknown",
2928
3180
  location: `${nodeDesc} deleted from position ${blockIndex}`,
2929
- preview: extractTextPreview(node),
3181
+ preview: extractTextPreview2(node),
2930
3182
  author,
2931
3183
  date: (/* @__PURE__ */ new Date()).toISOString()
2932
3184
  };
@@ -2985,8 +3237,9 @@ var DocxDiffEditor = forwardRef(
2985
3237
  const mountedRef = useRef(true);
2986
3238
  const initRef = useRef(false);
2987
3239
  const readyRef = useRef(false);
3240
+ const rollbackJsonRef = useRef(null);
2988
3241
  const [isLoading, setIsLoading] = useState(true);
2989
- const [error, setError] = useState(null);
3242
+ const [fatalError, setFatalError] = useState(null);
2990
3243
  const [sourceJson, setSourceJson] = useState(null);
2991
3244
  const [mergedJson, setMergedJson] = useState(null);
2992
3245
  const [diffResult, setDiffResult] = useState(null);
@@ -3043,11 +3296,31 @@ var DocxDiffEditor = forwardRef(
3043
3296
  sd.setTrackedChangesPreferences({ mode: "off", enabled: false });
3044
3297
  }
3045
3298
  }, []);
3046
- const handleError = useCallback(
3047
- (err) => {
3048
- const error2 = err instanceof Error ? err : new Error(err);
3049
- setError(error2.message);
3050
- onError?.(error2);
3299
+ const handleFatalError = useCallback(
3300
+ (err, operation) => {
3301
+ const error = err instanceof Error ? err : new Error(err);
3302
+ setFatalError(error.message);
3303
+ onError?.({
3304
+ error,
3305
+ type: "fatal",
3306
+ operation,
3307
+ recoverable: false,
3308
+ message: error.message
3309
+ });
3310
+ },
3311
+ [onError]
3312
+ );
3313
+ const handleOperationError = useCallback(
3314
+ (err, operation, phase) => {
3315
+ const error = err instanceof Error ? err : new Error(err);
3316
+ onError?.({
3317
+ error,
3318
+ type: "operation",
3319
+ operation,
3320
+ recoverable: true,
3321
+ message: error.message,
3322
+ phase
3323
+ });
3051
3324
  },
3052
3325
  [onError]
3053
3326
  );
@@ -3205,7 +3478,7 @@ var DocxDiffEditor = forwardRef(
3205
3478
  return;
3206
3479
  }
3207
3480
  setIsLoading(true);
3208
- setError(null);
3481
+ setFatalError(null);
3209
3482
  destroySuperdoc();
3210
3483
  try {
3211
3484
  const { SuperDoc } = await import('superdoc');
@@ -3228,17 +3501,19 @@ var DocxDiffEditor = forwardRef(
3228
3501
  if (sd?.activeEditor && isProseMirrorJSON(initialSource)) {
3229
3502
  setEditorContent(sd.activeEditor, initialSource);
3230
3503
  setSourceJson(initialSource);
3504
+ rollbackJsonRef.current = initialSource;
3231
3505
  onSourceLoaded?.(initialSource);
3232
3506
  }
3233
3507
  } else {
3234
3508
  setSourceJson(json);
3509
+ rollbackJsonRef.current = json;
3235
3510
  onSourceLoaded?.(json);
3236
3511
  }
3237
3512
  setIsLoading(false);
3238
3513
  onReady?.();
3239
3514
  } catch (err) {
3240
3515
  console.error("Failed to initialize SuperDoc:", err);
3241
- handleError(err instanceof Error ? err : new Error("Failed to load editor"));
3516
+ handleFatalError(err instanceof Error ? err : new Error("Failed to load editor"), "init");
3242
3517
  setIsLoading(false);
3243
3518
  initRef.current = false;
3244
3519
  }
@@ -3252,7 +3527,7 @@ var DocxDiffEditor = forwardRef(
3252
3527
  destroySuperdoc,
3253
3528
  createSuperdoc,
3254
3529
  setEditorContent,
3255
- handleError
3530
+ handleFatalError
3256
3531
  ]);
3257
3532
  useEffect(() => {
3258
3533
  mountedRef.current = true;
@@ -3287,42 +3562,98 @@ var DocxDiffEditor = forwardRef(
3287
3562
  * Accepts File (DOCX), HTML string, or ProseMirror JSON.
3288
3563
  * Note: This destroys and recreates the SuperDoc instance.
3289
3564
  * For JSON content updates, prefer updateContent() to preserve the existing template.
3565
+ *
3566
+ * On failure, attempts to preserve the previous editor state.
3567
+ * Returns void on success, or SetSourceError on recoverable failure.
3290
3568
  */
3291
3569
  async setSource(content) {
3292
3570
  if (!SuperDocRef.current) {
3293
- throw new Error("Editor not initialized");
3571
+ const error = new Error("Editor not initialized");
3572
+ handleOperationError(error, "setSource");
3573
+ return { success: false, error, message: error.message };
3294
3574
  }
3575
+ const currentRollback = superdocRef.current?.activeEditor ? superdocRef.current.activeEditor.getJSON() : rollbackJsonRef.current;
3295
3576
  setIsLoading(true);
3296
- setError(null);
3577
+ setFatalError(null);
3297
3578
  try {
3298
3579
  const contentType = detectContentType(content);
3299
- let json;
3300
- destroySuperdoc();
3301
- if (contentType === "file") {
3302
- const result = await createSuperdoc({ document: content });
3303
- json = result.json;
3304
- } else if (contentType === "html") {
3305
- const result = await createSuperdoc({ html: content });
3306
- json = result.json;
3307
- } else {
3308
- const result = await createSuperdoc(templateDocx ? { document: templateDocx } : {});
3309
- if (result.superdoc?.activeEditor && isProseMirrorJSON(content)) {
3310
- setEditorContent(result.superdoc.activeEditor, content);
3311
- json = content;
3580
+ let validatedJson = null;
3581
+ try {
3582
+ if (contentType === "file") {
3583
+ validatedJson = await parseDocxFile(content, SuperDocRef.current);
3584
+ } else if (contentType === "html") {
3585
+ validatedJson = await parseHtmlToJson(content, SuperDocRef.current);
3312
3586
  } else {
3587
+ if (!isProseMirrorJSON(content)) {
3588
+ throw new Error("Invalid ProseMirror JSON structure");
3589
+ }
3590
+ validatedJson = content;
3591
+ }
3592
+ } catch (parseErr) {
3593
+ const error = parseErr instanceof Error ? parseErr : new Error("Failed to parse content");
3594
+ handleOperationError(error, "setSource");
3595
+ setIsLoading(false);
3596
+ return { success: false, error, message: error.message };
3597
+ }
3598
+ destroySuperdoc();
3599
+ let json;
3600
+ try {
3601
+ if (contentType === "file") {
3602
+ const result = await createSuperdoc({ document: content });
3603
+ json = result.json;
3604
+ } else if (contentType === "html") {
3605
+ const result = await createSuperdoc({ html: content });
3313
3606
  json = result.json;
3607
+ } else {
3608
+ const result = await createSuperdoc(templateDocx ? { document: templateDocx } : {});
3609
+ if (result.superdoc?.activeEditor && validatedJson) {
3610
+ setEditorContent(result.superdoc.activeEditor, validatedJson);
3611
+ json = validatedJson;
3612
+ } else {
3613
+ json = result.json;
3614
+ }
3615
+ }
3616
+ } catch (createErr) {
3617
+ console.warn("[DocxDiffEditor] Failed to create new editor, attempting recovery:", createErr);
3618
+ if (currentRollback) {
3619
+ try {
3620
+ const recoveryResult = await createSuperdoc(templateDocx ? { document: templateDocx } : {});
3621
+ if (recoveryResult.superdoc?.activeEditor) {
3622
+ setEditorContent(recoveryResult.superdoc.activeEditor, currentRollback);
3623
+ setSourceJson(currentRollback);
3624
+ setEditingMode(superdocRef.current);
3625
+ }
3626
+ const error = createErr instanceof Error ? createErr : new Error("Failed to set source");
3627
+ handleOperationError(error, "setSource");
3628
+ setIsLoading(false);
3629
+ return { success: false, error, message: error.message };
3630
+ } catch (recoveryErr) {
3631
+ console.error("[DocxDiffEditor] Recovery failed:", recoveryErr);
3632
+ const error = createErr instanceof Error ? createErr : new Error("Failed to set source");
3633
+ handleFatalError(error, "setSource");
3634
+ setIsLoading(false);
3635
+ return { success: false, error, message: error.message };
3636
+ }
3637
+ } else {
3638
+ const error = createErr instanceof Error ? createErr : new Error("Failed to set source");
3639
+ handleFatalError(error, "setSource");
3640
+ setIsLoading(false);
3641
+ return { success: false, error, message: error.message };
3314
3642
  }
3315
3643
  }
3316
3644
  setSourceJson(json);
3317
3645
  setMergedJson(null);
3318
3646
  setDiffResult(null);
3319
3647
  setEditingMode(superdocRef.current);
3648
+ rollbackJsonRef.current = json;
3320
3649
  onSourceLoaded?.(json);
3650
+ setIsLoading(false);
3651
+ return;
3321
3652
  } catch (err) {
3322
- handleError(err instanceof Error ? err : new Error("Failed to set source"));
3323
- throw err;
3324
- } finally {
3653
+ const error = err instanceof Error ? err : new Error("Failed to set source");
3654
+ handleFatalError(error, "setSource");
3325
3655
  setIsLoading(false);
3656
+ return { success: false, error, message: error.message };
3326
3657
  }
3327
3658
  },
3328
3659
  /**
@@ -3334,21 +3665,27 @@ var DocxDiffEditor = forwardRef(
3334
3665
  *
3335
3666
  * To compare against the original source document, call setSource() again
3336
3667
  * before compareWith().
3668
+ *
3669
+ * Returns a union type - check `result.success` to determine outcome:
3670
+ * - `success: true` - Comparison succeeded
3671
+ * - `success: false` - Comparison failed, editor unchanged
3337
3672
  */
3338
3673
  async compareWith(content) {
3339
3674
  if (!SuperDocRef.current) {
3340
- throw new Error("Editor not initialized");
3675
+ const error = new Error("Editor not initialized");
3676
+ handleOperationError(error, "compareWith", "parsing");
3677
+ return { success: false, error, message: error.message, phase: "parsing" };
3341
3678
  }
3342
3679
  if (!superdocRef.current?.activeEditor) {
3343
- throw new Error("Editor not ready. Ensure a document is loaded first.");
3680
+ const error = new Error("Editor not ready. Ensure a document is loaded first.");
3681
+ handleOperationError(error, "compareWith", "parsing");
3682
+ return { success: false, error, message: error.message, phase: "parsing" };
3344
3683
  }
3684
+ const rollbackJson = superdocRef.current.activeEditor.getJSON();
3345
3685
  setIsLoading(true);
3686
+ let newJson;
3687
+ const contentType = detectContentType(content);
3346
3688
  try {
3347
- const currentEditorJson = superdocRef.current.activeEditor.getJSON();
3348
- const cleanBaseline = acceptAllChangesInJson(currentEditorJson) || { type: "doc", content: [] };
3349
- setSourceJson(cleanBaseline);
3350
- const contentType = detectContentType(content);
3351
- let newJson;
3352
3689
  if (contentType === "file") {
3353
3690
  newJson = await parseDocxFile(content, SuperDocRef.current);
3354
3691
  } else if (contentType === "html") {
@@ -3403,91 +3740,122 @@ var DocxDiffEditor = forwardRef(
3403
3740
  }
3404
3741
  newJson = content;
3405
3742
  }
3743
+ } catch (parseErr) {
3744
+ const error = parseErr instanceof Error ? parseErr : new Error("Failed to parse content");
3745
+ handleOperationError(error, "compareWith", "parsing");
3746
+ setIsLoading(false);
3747
+ return { success: false, error, message: error.message, phase: "parsing" };
3748
+ }
3749
+ let normalizedMerged;
3750
+ let normalizedNewJson;
3751
+ let structInfos;
3752
+ let diff;
3753
+ let cleanBaseline;
3754
+ try {
3755
+ const currentEditorJson = superdocRef.current.activeEditor.getJSON();
3756
+ cleanBaseline = acceptAllChangesInJson(currentEditorJson) || { type: "doc", content: [] };
3406
3757
  const cleanNewJson = acceptAllChangesInJson(newJson) || { type: "doc", content: [] };
3407
- const normalizedNewJson = normalizeRunProperties(cleanNewJson);
3758
+ normalizedNewJson = normalizeRunProperties(cleanNewJson);
3408
3759
  const structuralResult = mergeWithStructuralAwareness(
3409
3760
  cleanBaseline,
3410
3761
  normalizedNewJson,
3411
3762
  author
3412
3763
  );
3413
3764
  const merged = structuralResult.mergedDoc;
3414
- const structInfos = structuralResult.structuralInfos;
3415
- const normalizedMerged = normalizeRunProperties(merged);
3416
- setMergedJson(normalizedMerged);
3417
- const diff = diffDocuments(cleanBaseline, cleanNewJson);
3418
- setDiffResult(diff);
3419
- let usedFallback = false;
3420
- if (superdocRef.current?.activeEditor) {
3421
- try {
3422
- setEditorContent(superdocRef.current.activeEditor, normalizedMerged);
3423
- enableReviewMode(superdocRef.current);
3424
- const sd = superdocRef.current;
3425
- if (sd.commentsStore?.processLoadedDocxComments) {
3426
- setTimeout(() => {
3427
- try {
3428
- sd.commentsStore.processLoadedDocxComments({
3429
- superdoc: sd,
3430
- editor: sd.activeEditor,
3431
- comments: [],
3432
- // Empty array - we just want to trigger createCommentForTrackChanges
3433
- documentId: sd.activeEditor?.options?.documentId || "primary"
3434
- });
3435
- } catch (err) {
3436
- console.warn("[DocxDiffEditor] Failed to process track changes for bubbles:", err);
3437
- }
3438
- }, 50);
3439
- }
3440
- } catch (contentErr) {
3441
- console.warn(
3442
- "[DocxDiffEditor] Failed to apply merged content with track changes. Falling back to direct content update without track bubbles.",
3443
- contentErr
3444
- );
3445
- usedFallback = true;
3765
+ structInfos = structuralResult.structuralInfos;
3766
+ normalizedMerged = normalizeRunProperties(merged);
3767
+ diff = diffDocuments(cleanBaseline, cleanNewJson);
3768
+ } catch (mergeErr) {
3769
+ const error = mergeErr instanceof Error ? mergeErr : new Error("Failed to merge documents");
3770
+ handleOperationError(error, "compareWith", "merging");
3771
+ setIsLoading(false);
3772
+ return { success: false, error, message: error.message, phase: "merging" };
3773
+ }
3774
+ let usedFallback = false;
3775
+ try {
3776
+ setEditorContent(superdocRef.current.activeEditor, normalizedMerged);
3777
+ enableReviewMode(superdocRef.current);
3778
+ const sd = superdocRef.current;
3779
+ if (sd.commentsStore?.processLoadedDocxComments) {
3780
+ setTimeout(() => {
3446
3781
  try {
3447
- setEditorContent(superdocRef.current.activeEditor, normalizedNewJson);
3448
- setEditingMode(superdocRef.current);
3449
- } catch (fallbackErr) {
3450
- console.error("[DocxDiffEditor] Fallback content update also failed:", fallbackErr);
3451
- throw contentErr;
3782
+ sd.commentsStore.processLoadedDocxComments({
3783
+ superdoc: sd,
3784
+ editor: sd.activeEditor,
3785
+ comments: [],
3786
+ documentId: sd.activeEditor?.options?.documentId || "primary"
3787
+ });
3788
+ } catch (err) {
3789
+ console.warn("[DocxDiffEditor] Failed to process track changes for bubbles:", err);
3452
3790
  }
3453
- }
3454
- }
3455
- if (!usedFallback) {
3456
- setStructuralChanges(structInfos);
3457
- setIsPaneDismissed(false);
3458
- } else {
3459
- setStructuralChanges([]);
3791
+ }, 50);
3460
3792
  }
3461
- const insertions = diff.segments.filter((s) => s.type === "insert").length;
3462
- const deletions = diff.segments.filter((s) => s.type === "delete").length;
3463
- const formatChanges = diff.formatChanges?.length || 0;
3464
- const structuralChangeCount = usedFallback ? 0 : structInfos.length;
3465
- const combinedSummary = [...structuralResult.summary];
3466
- if (diff.summary.length > 0 && structuralResult.summary.length === 0) {
3467
- combinedSummary.push(...diff.summary);
3793
+ } catch (contentErr) {
3794
+ console.warn(
3795
+ "[DocxDiffEditor] Failed to apply merged content with track changes. Falling back to direct content update without track bubbles.",
3796
+ contentErr
3797
+ );
3798
+ usedFallback = true;
3799
+ try {
3800
+ setEditorContent(superdocRef.current.activeEditor, normalizedNewJson);
3801
+ setEditingMode(superdocRef.current);
3802
+ } catch (fallbackErr) {
3803
+ console.warn("[DocxDiffEditor] Fallback content update failed, attempting rollback:", fallbackErr);
3804
+ try {
3805
+ setEditorContent(superdocRef.current.activeEditor, rollbackJson);
3806
+ setEditingMode(superdocRef.current);
3807
+ const error = contentErr instanceof Error ? contentErr : new Error("Failed to apply comparison");
3808
+ handleOperationError(error, "compareWith", "applying");
3809
+ setIsLoading(false);
3810
+ return { success: false, error, message: error.message, phase: "applying" };
3811
+ } catch (rollbackErr) {
3812
+ console.error("[DocxDiffEditor] All recovery attempts failed:", rollbackErr);
3813
+ const error = contentErr instanceof Error ? contentErr : new Error("Failed to apply comparison");
3814
+ handleFatalError(error, "compareWith");
3815
+ setIsLoading(false);
3816
+ return { success: false, error, message: error.message, phase: "applying" };
3817
+ }
3468
3818
  }
3469
- if (usedFallback) {
3470
- combinedSummary.push("Note: Track change visualization unavailable for this content");
3819
+ }
3820
+ setSourceJson(cleanBaseline);
3821
+ setMergedJson(normalizedMerged);
3822
+ setDiffResult(diff);
3823
+ rollbackJsonRef.current = usedFallback ? normalizedNewJson : normalizedMerged;
3824
+ if (!usedFallback) {
3825
+ setStructuralChanges(structInfos);
3826
+ setIsPaneDismissed(false);
3827
+ } else {
3828
+ setStructuralChanges([]);
3829
+ }
3830
+ const insertions = diff.segments.filter((s) => s.type === "insert").length;
3831
+ const deletions = diff.segments.filter((s) => s.type === "delete").length;
3832
+ const formatChanges = diff.formatChanges?.length || 0;
3833
+ const structuralChangeCount = usedFallback ? 0 : structInfos.length;
3834
+ const combinedSummary = [...diff.summary || []];
3835
+ if (structInfos.length > 0 && !usedFallback) {
3836
+ const structSummaries = generateStructuralChangeSummary(structInfos);
3837
+ if (structSummaries.length > 0) {
3838
+ combinedSummary.unshift(...structSummaries);
3471
3839
  }
3472
- const result = {
3473
- totalChanges: insertions + deletions + formatChanges + structuralChangeCount,
3474
- insertions,
3475
- deletions,
3476
- formatChanges,
3477
- structuralChanges: structuralChangeCount,
3478
- summary: combinedSummary,
3479
- mergedJson: usedFallback ? normalizedNewJson : merged,
3480
- structuralChangeInfos: usedFallback ? [] : structInfos,
3481
- usedFallback
3482
- };
3483
- onComparisonComplete?.(result);
3484
- return result;
3485
- } catch (err) {
3486
- handleError(err instanceof Error ? err : new Error("Comparison failed"));
3487
- throw err;
3488
- } finally {
3489
- setIsLoading(false);
3490
3840
  }
3841
+ if (usedFallback) {
3842
+ combinedSummary.push("Note: Track change visualization unavailable for this content");
3843
+ }
3844
+ const result = {
3845
+ success: true,
3846
+ totalChanges: insertions + deletions + formatChanges + structuralChangeCount,
3847
+ insertions,
3848
+ deletions,
3849
+ formatChanges,
3850
+ structuralChanges: structuralChangeCount,
3851
+ summary: combinedSummary,
3852
+ mergedJson: usedFallback ? normalizedNewJson : normalizedMerged,
3853
+ structuralChangeInfos: usedFallback ? [] : structInfos,
3854
+ usedFallback
3855
+ };
3856
+ onComparisonComplete?.(result);
3857
+ setIsLoading(false);
3858
+ return result;
3491
3859
  },
3492
3860
  /**
3493
3861
  * Get raw diff segments
@@ -3815,7 +4183,8 @@ var DocxDiffEditor = forwardRef(
3815
4183
  setEditingMode,
3816
4184
  onSourceLoaded,
3817
4185
  onComparisonComplete,
3818
- handleError
4186
+ handleFatalError,
4187
+ handleOperationError
3819
4188
  ]
3820
4189
  );
3821
4190
  return /* @__PURE__ */ jsxs("div", { className: `dde-container ${className}`.trim(), children: [
@@ -3823,7 +4192,7 @@ var DocxDiffEditor = forwardRef(
3823
4192
  /* @__PURE__ */ jsx("div", { className: "dde-loading__spinner" }),
3824
4193
  /* @__PURE__ */ jsx("p", { className: "dde-loading__text", children: "Loading document..." })
3825
4194
  ] }),
3826
- error && /* @__PURE__ */ jsxs("div", { className: "dde-error", children: [
4195
+ fatalError && /* @__PURE__ */ jsxs("div", { className: "dde-error", children: [
3827
4196
  /* @__PURE__ */ jsx("div", { className: "dde-error__icon", children: /* @__PURE__ */ jsx(
3828
4197
  "svg",
3829
4198
  {
@@ -3843,7 +4212,7 @@ var DocxDiffEditor = forwardRef(
3843
4212
  }
3844
4213
  ) }),
3845
4214
  /* @__PURE__ */ jsx("p", { className: "dde-error__title", children: "Failed to load document" }),
3846
- /* @__PURE__ */ jsx("p", { className: "dde-error__message", children: error })
4215
+ /* @__PURE__ */ jsx("p", { className: "dde-error__message", children: fatalError })
3847
4216
  ] }),
3848
4217
  showToolbar && /* @__PURE__ */ jsx(
3849
4218
  "div",
@@ -3880,256 +4249,6 @@ var DocxDiffEditor_default = DocxDiffEditor;
3880
4249
 
3881
4250
  // src/services/index.ts
3882
4251
  init_nodeFingerprint();
3883
- function markAllTextAsInserted2(node, sharedId, author) {
3884
- if (node.type === "text") {
3885
- const existingMarks = normalizeMarksForRendering(node.marks || []);
3886
- return {
3887
- ...node,
3888
- marks: [...existingMarks, createTrackInsertMark(author, sharedId)]
3889
- };
3890
- }
3891
- if (node.content && Array.isArray(node.content)) {
3892
- return {
3893
- ...node,
3894
- content: node.content.map(
3895
- (child) => markAllTextAsInserted2(child, sharedId, author)
3896
- )
3897
- };
3898
- }
3899
- return node;
3900
- }
3901
- function markAllTextAsDeleted2(node, sharedId, author) {
3902
- if (node.type === "text") {
3903
- const existingMarks = normalizeMarksForRendering(node.marks || []);
3904
- return {
3905
- ...node,
3906
- marks: [...existingMarks, createTrackDeleteMark(author, sharedId)]
3907
- };
3908
- }
3909
- if (node.content && Array.isArray(node.content)) {
3910
- return {
3911
- ...node,
3912
- content: node.content.map(
3913
- (child) => markAllTextAsDeleted2(child, sharedId, author)
3914
- )
3915
- };
3916
- }
3917
- return node;
3918
- }
3919
- function cloneNode3(node) {
3920
- return JSON.parse(JSON.stringify(node));
3921
- }
3922
- function extractTextPreview2(node, maxLength = 50) {
3923
- const texts = [];
3924
- function extract(n) {
3925
- if (n.type === "text") {
3926
- texts.push(n.text || "");
3927
- }
3928
- if (n.content) {
3929
- for (const child of n.content) {
3930
- extract(child);
3931
- }
3932
- }
3933
- }
3934
- extract(node);
3935
- const text = texts.join("").trim();
3936
- if (text.length > maxLength) {
3937
- return text.substring(0, maxLength - 3) + "...";
3938
- }
3939
- return text || "(empty)";
3940
- }
3941
- function processStructuralChanges(docA, docB, author = DEFAULT_AUTHOR) {
3942
- const changes = [];
3943
- const infos = [];
3944
- const alignment = alignDocuments(docA, docB);
3945
- let tableIndex = 0;
3946
- let listIndex = 0;
3947
- let paragraphIndex = 0;
3948
- for (const inserted of alignment.insertions) {
3949
- const node = inserted.node;
3950
- const sharedId = v4();
3951
- const date = (/* @__PURE__ */ new Date()).toISOString();
3952
- let type = "paragraphInsert";
3953
- let location = "";
3954
- let preview = "";
3955
- if (isTable(node)) {
3956
- type = "rowInsert";
3957
- location = `New table at position ${inserted.path[0] + 1}`;
3958
- preview = `Table with ${node.content?.length || 0} rows`;
3959
- tableIndex++;
3960
- } else if (isList(node)) {
3961
- type = "listItemInsert";
3962
- location = `New list at position ${inserted.path[0] + 1}`;
3963
- preview = `List with ${node.content?.length || 0} items`;
3964
- listIndex++;
3965
- } else {
3966
- type = "paragraphInsert";
3967
- paragraphIndex++;
3968
- location = `Paragraph ${paragraphIndex}`;
3969
- preview = extractTextPreview2(node);
3970
- }
3971
- changes.push({
3972
- id: sharedId,
3973
- type,
3974
- nodeType: node.type,
3975
- path: inserted.path,
3976
- node: markAllTextAsInserted2(cloneNode3(node), sharedId, author)
3977
- });
3978
- infos.push({
3979
- id: sharedId,
3980
- type,
3981
- nodeType: node.type,
3982
- location,
3983
- preview,
3984
- author,
3985
- date
3986
- });
3987
- }
3988
- for (const deleted of alignment.deletions) {
3989
- const node = deleted.node;
3990
- const sharedId = v4();
3991
- const date = (/* @__PURE__ */ new Date()).toISOString();
3992
- let type = "paragraphDelete";
3993
- let location = "";
3994
- let preview = "";
3995
- if (isTable(node)) {
3996
- type = "rowDelete";
3997
- location = `Deleted table at position ${deleted.path[0] + 1}`;
3998
- preview = `Table with ${node.content?.length || 0} rows`;
3999
- } else if (isList(node)) {
4000
- type = "listItemDelete";
4001
- location = `Deleted list at position ${deleted.path[0] + 1}`;
4002
- preview = `List with ${node.content?.length || 0} items`;
4003
- } else {
4004
- type = "paragraphDelete";
4005
- location = `Deleted paragraph`;
4006
- preview = extractTextPreview2(node);
4007
- }
4008
- changes.push({
4009
- id: sharedId,
4010
- type,
4011
- nodeType: node.type,
4012
- path: deleted.path,
4013
- node: markAllTextAsDeleted2(cloneNode3(node), sharedId, author)
4014
- });
4015
- infos.push({
4016
- id: sharedId,
4017
- type,
4018
- nodeType: node.type,
4019
- location,
4020
- preview,
4021
- author,
4022
- date
4023
- });
4024
- }
4025
- for (const match of alignment.matched) {
4026
- const nodeA = docA.content?.[match.pathA[0]];
4027
- const nodeB = docB.content?.[match.pathB[0]];
4028
- if (!nodeA || !nodeB) continue;
4029
- if (isTable(nodeA) && isTable(nodeB)) {
4030
- tableIndex++;
4031
- const tableResult = diffTables(nodeA, nodeB, match.pathA, match.pathB);
4032
- for (const rowChange of tableResult.rowChanges) {
4033
- const sharedId = rowChange.id;
4034
- const date = (/* @__PURE__ */ new Date()).toISOString();
4035
- const rowIndex = rowChange.path[rowChange.path.length - 1];
4036
- const isInsert = rowChange.type === "rowInsert";
4037
- const location = getRowLocation(rowChange.path, rowIndex, tableIndex - 1);
4038
- const preview = getRowPreview(rowChange.node);
4039
- const markedNode = isInsert ? markAllTextAsInserted2(cloneNode3(rowChange.node), sharedId, author) : markAllTextAsDeleted2(cloneNode3(rowChange.node), sharedId, author);
4040
- changes.push({
4041
- ...rowChange,
4042
- node: markedNode
4043
- });
4044
- infos.push({
4045
- id: sharedId,
4046
- type: rowChange.type,
4047
- nodeType: "tableRow",
4048
- location,
4049
- preview,
4050
- author,
4051
- date
4052
- });
4053
- }
4054
- }
4055
- if (isList(nodeA) && isList(nodeB)) {
4056
- listIndex++;
4057
- const listResult = diffLists(nodeA, nodeB, match.pathA, match.pathB);
4058
- for (const itemChange of listResult.itemChanges) {
4059
- const sharedId = itemChange.id;
4060
- const date = (/* @__PURE__ */ new Date()).toISOString();
4061
- const itemIndex = itemChange.path[itemChange.path.length - 1];
4062
- const isInsert = itemChange.type === "listItemInsert";
4063
- const location = getListItemLocation(itemChange.path, itemIndex, listIndex - 1);
4064
- const preview = getListItemPreview(itemChange.node);
4065
- const markedNode = isInsert ? markAllTextAsInserted2(cloneNode3(itemChange.node), sharedId, author) : markAllTextAsDeleted2(cloneNode3(itemChange.node), sharedId, author);
4066
- changes.push({
4067
- ...itemChange,
4068
- node: markedNode
4069
- });
4070
- infos.push({
4071
- id: sharedId,
4072
- type: itemChange.type,
4073
- nodeType: "listItem",
4074
- location,
4075
- preview,
4076
- author,
4077
- date
4078
- });
4079
- }
4080
- }
4081
- }
4082
- const imageChanges = diffImages(docA, docB);
4083
- for (const imgInsert of imageChanges.inserted) {
4084
- const sharedId = imgInsert.id;
4085
- const date = (/* @__PURE__ */ new Date()).toISOString();
4086
- infos.push({
4087
- id: sharedId,
4088
- type: "imageInsert",
4089
- nodeType: "image",
4090
- location: getImageLocation(imgInsert.path),
4091
- preview: getImagePreview(imgInsert.node),
4092
- author,
4093
- date
4094
- });
4095
- changes.push(imgInsert);
4096
- }
4097
- for (const imgDelete of imageChanges.deleted) {
4098
- const sharedId = imgDelete.id;
4099
- const date = (/* @__PURE__ */ new Date()).toISOString();
4100
- infos.push({
4101
- id: sharedId,
4102
- type: "imageDelete",
4103
- nodeType: "image",
4104
- location: getImageLocation(imgDelete.path),
4105
- preview: getImagePreview(imgDelete.node),
4106
- author,
4107
- date
4108
- });
4109
- changes.push(imgDelete);
4110
- }
4111
- return { changes, infos };
4112
- }
4113
- function generateStructuralChangeSummary(infos) {
4114
- const summary = [];
4115
- const rowInserts = infos.filter((i) => i.type === "rowInsert").length;
4116
- const rowDeletes = infos.filter((i) => i.type === "rowDelete").length;
4117
- const paragraphInserts = infos.filter((i) => i.type === "paragraphInsert").length;
4118
- const paragraphDeletes = infos.filter((i) => i.type === "paragraphDelete").length;
4119
- const listItemInserts = infos.filter((i) => i.type === "listItemInsert").length;
4120
- const listItemDeletes = infos.filter((i) => i.type === "listItemDelete").length;
4121
- const imageInserts = infos.filter((i) => i.type === "imageInsert").length;
4122
- const imageDeletes = infos.filter((i) => i.type === "imageDelete").length;
4123
- if (rowInserts > 0) summary.push(`${rowInserts} row(s) inserted`);
4124
- if (rowDeletes > 0) summary.push(`${rowDeletes} row(s) deleted`);
4125
- if (paragraphInserts > 0) summary.push(`${paragraphInserts} paragraph(s) inserted`);
4126
- if (paragraphDeletes > 0) summary.push(`${paragraphDeletes} paragraph(s) deleted`);
4127
- if (listItemInserts > 0) summary.push(`${listItemInserts} list item(s) inserted`);
4128
- if (listItemDeletes > 0) summary.push(`${listItemDeletes} list item(s) deleted`);
4129
- if (imageInserts > 0) summary.push(`${imageInserts} image(s) inserted`);
4130
- if (imageDeletes > 0) summary.push(`${imageDeletes} image(s) deleted`);
4131
- return summary;
4132
- }
4133
4252
 
4134
4253
  // src/blankTemplate.ts
4135
4254
  var BLANK_DOCX_BASE64 = `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`;