docx-diff-editor 1.0.45 → 1.0.47
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.d.mts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +276 -26
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +276 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -29,6 +29,10 @@ type DocxContent = File | ProseMirrorJSON | string;
|
|
|
29
29
|
interface DiffSegment {
|
|
30
30
|
type: 'equal' | 'insert' | 'delete';
|
|
31
31
|
text: string;
|
|
32
|
+
/** Position in docA where this segment starts (for equal/delete segments) */
|
|
33
|
+
posA?: number;
|
|
34
|
+
/** Position in docB where this segment starts (for equal/insert segments) */
|
|
35
|
+
posB?: number;
|
|
32
36
|
}
|
|
33
37
|
/**
|
|
34
38
|
* A format change on unchanged text
|
|
@@ -54,6 +58,8 @@ interface DiffResult {
|
|
|
54
58
|
textB: string;
|
|
55
59
|
/** Human-readable summary */
|
|
56
60
|
summary: string[];
|
|
61
|
+
/** Text spans from docB with marks (for mark preservation during merge) */
|
|
62
|
+
spansB?: TextSpan[];
|
|
57
63
|
}
|
|
58
64
|
/**
|
|
59
65
|
* Type of structural change
|
|
@@ -365,6 +371,15 @@ interface DocxDiffEditorRef {
|
|
|
365
371
|
/** Parse HTML string to ProseMirror JSON (uses hidden SuperDoc instance) */
|
|
366
372
|
parseHtml(html: string): Promise<ProseMirrorJSON>;
|
|
367
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* Text span with position and marks (used in diffing)
|
|
376
|
+
*/
|
|
377
|
+
interface TextSpan {
|
|
378
|
+
text: string;
|
|
379
|
+
from: number;
|
|
380
|
+
to: number;
|
|
381
|
+
marks: ProseMirrorMark[];
|
|
382
|
+
}
|
|
368
383
|
|
|
369
384
|
/**
|
|
370
385
|
* DocxDiffEditor Component
|
|
@@ -454,6 +469,10 @@ declare function parseDocxFile(file: File, SuperDoc: SuperDocConstructor): Promi
|
|
|
454
469
|
/**
|
|
455
470
|
* Diff two ProseMirror JSON documents at the character level.
|
|
456
471
|
* Detects both text changes and formatting changes.
|
|
472
|
+
*
|
|
473
|
+
* Now also tracks positions in both documents for mark preservation:
|
|
474
|
+
* - posA: position in docA (for equal/delete segments)
|
|
475
|
+
* - posB: position in docB (for equal/insert segments)
|
|
457
476
|
*/
|
|
458
477
|
declare function diffDocuments(docA: ProseMirrorJSON, docB: ProseMirrorJSON): DiffResult;
|
|
459
478
|
|
package/dist/index.d.ts
CHANGED
|
@@ -29,6 +29,10 @@ type DocxContent = File | ProseMirrorJSON | string;
|
|
|
29
29
|
interface DiffSegment {
|
|
30
30
|
type: 'equal' | 'insert' | 'delete';
|
|
31
31
|
text: string;
|
|
32
|
+
/** Position in docA where this segment starts (for equal/delete segments) */
|
|
33
|
+
posA?: number;
|
|
34
|
+
/** Position in docB where this segment starts (for equal/insert segments) */
|
|
35
|
+
posB?: number;
|
|
32
36
|
}
|
|
33
37
|
/**
|
|
34
38
|
* A format change on unchanged text
|
|
@@ -54,6 +58,8 @@ interface DiffResult {
|
|
|
54
58
|
textB: string;
|
|
55
59
|
/** Human-readable summary */
|
|
56
60
|
summary: string[];
|
|
61
|
+
/** Text spans from docB with marks (for mark preservation during merge) */
|
|
62
|
+
spansB?: TextSpan[];
|
|
57
63
|
}
|
|
58
64
|
/**
|
|
59
65
|
* Type of structural change
|
|
@@ -365,6 +371,15 @@ interface DocxDiffEditorRef {
|
|
|
365
371
|
/** Parse HTML string to ProseMirror JSON (uses hidden SuperDoc instance) */
|
|
366
372
|
parseHtml(html: string): Promise<ProseMirrorJSON>;
|
|
367
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* Text span with position and marks (used in diffing)
|
|
376
|
+
*/
|
|
377
|
+
interface TextSpan {
|
|
378
|
+
text: string;
|
|
379
|
+
from: number;
|
|
380
|
+
to: number;
|
|
381
|
+
marks: ProseMirrorMark[];
|
|
382
|
+
}
|
|
368
383
|
|
|
369
384
|
/**
|
|
370
385
|
* DocxDiffEditor Component
|
|
@@ -454,6 +469,10 @@ declare function parseDocxFile(file: File, SuperDoc: SuperDocConstructor): Promi
|
|
|
454
469
|
/**
|
|
455
470
|
* Diff two ProseMirror JSON documents at the character level.
|
|
456
471
|
* Detects both text changes and formatting changes.
|
|
472
|
+
*
|
|
473
|
+
* Now also tracks positions in both documents for mark preservation:
|
|
474
|
+
* - posA: position in docA (for equal/delete segments)
|
|
475
|
+
* - posB: position in docB (for equal/insert segments)
|
|
457
476
|
*/
|
|
458
477
|
declare function diffDocuments(docA: ProseMirrorJSON, docB: ProseMirrorJSON): DiffResult;
|
|
459
478
|
|
package/dist/index.js
CHANGED
|
@@ -453,6 +453,170 @@ var TIMEOUTS = {
|
|
|
453
453
|
CLEANUP_DELAY: 100
|
|
454
454
|
};
|
|
455
455
|
|
|
456
|
+
// src/services/runPropertiesSync.ts
|
|
457
|
+
var PT_TO_TWIPS = 20;
|
|
458
|
+
function ptToTwips(ptValue) {
|
|
459
|
+
return Math.round(ptValue * PT_TO_TWIPS);
|
|
460
|
+
}
|
|
461
|
+
function stripHashFromColor(color) {
|
|
462
|
+
return color.replace(/^#/, "");
|
|
463
|
+
}
|
|
464
|
+
function parseFontSizeToPoints(fontSize) {
|
|
465
|
+
if (typeof fontSize === "number") {
|
|
466
|
+
return fontSize;
|
|
467
|
+
}
|
|
468
|
+
const value = parseFloat(fontSize);
|
|
469
|
+
if (isNaN(value)) {
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
if (fontSize.toLowerCase().includes("px")) {
|
|
473
|
+
return value * 0.75;
|
|
474
|
+
}
|
|
475
|
+
return value;
|
|
476
|
+
}
|
|
477
|
+
function cleanFontFamily(fontFamily) {
|
|
478
|
+
return fontFamily.split(",")[0].trim().replace(/^["']|["']$/g, "");
|
|
479
|
+
}
|
|
480
|
+
function marksToRunProperties(marks) {
|
|
481
|
+
const runProperties = {};
|
|
482
|
+
if (!marks || !Array.isArray(marks)) {
|
|
483
|
+
return runProperties;
|
|
484
|
+
}
|
|
485
|
+
for (const mark of marks) {
|
|
486
|
+
const type = mark.type;
|
|
487
|
+
const attrs = mark.attrs || {};
|
|
488
|
+
switch (type) {
|
|
489
|
+
// Boolean marks: bold, italic, strike
|
|
490
|
+
case "bold":
|
|
491
|
+
case "italic":
|
|
492
|
+
case "strike": {
|
|
493
|
+
const isNegated = attrs.value === "0" || attrs.value === false;
|
|
494
|
+
runProperties[type] = !isNegated;
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
497
|
+
// Underline with optional type and color
|
|
498
|
+
case "underline": {
|
|
499
|
+
const underlineAttrs = {};
|
|
500
|
+
if (attrs.underlineType) {
|
|
501
|
+
underlineAttrs["w:val"] = String(attrs.underlineType);
|
|
502
|
+
} else {
|
|
503
|
+
underlineAttrs["w:val"] = "single";
|
|
504
|
+
}
|
|
505
|
+
if (attrs.underlineColor) {
|
|
506
|
+
underlineAttrs["w:color"] = stripHashFromColor(String(attrs.underlineColor));
|
|
507
|
+
}
|
|
508
|
+
if (Object.keys(underlineAttrs).length > 0) {
|
|
509
|
+
runProperties.underline = underlineAttrs;
|
|
510
|
+
}
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
// Highlight (background color)
|
|
514
|
+
case "highlight": {
|
|
515
|
+
if (attrs.color) {
|
|
516
|
+
const color = String(attrs.color).toLowerCase();
|
|
517
|
+
if (color === "transparent") {
|
|
518
|
+
runProperties.highlight = { "w:val": "none" };
|
|
519
|
+
} else {
|
|
520
|
+
runProperties.highlight = { "w:val": color };
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
// textStyle contains multiple style attributes
|
|
526
|
+
case "textStyle": {
|
|
527
|
+
if (attrs.color != null) {
|
|
528
|
+
runProperties.color = {
|
|
529
|
+
val: stripHashFromColor(String(attrs.color))
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
if (attrs.fontSize != null) {
|
|
533
|
+
const points = parseFontSizeToPoints(attrs.fontSize);
|
|
534
|
+
if (points !== null) {
|
|
535
|
+
runProperties.fontSize = points * 2;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (attrs.fontFamily != null) {
|
|
539
|
+
const cleanedFont = cleanFontFamily(String(attrs.fontFamily));
|
|
540
|
+
runProperties.fontFamily = {
|
|
541
|
+
ascii: cleanedFont,
|
|
542
|
+
eastAsia: cleanedFont,
|
|
543
|
+
hAnsi: cleanedFont,
|
|
544
|
+
cs: cleanedFont
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
if (attrs.letterSpacing != null) {
|
|
548
|
+
const ptValue = parseFloat(String(attrs.letterSpacing));
|
|
549
|
+
if (!isNaN(ptValue)) {
|
|
550
|
+
runProperties.letterSpacing = ptToTwips(ptValue);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (attrs.textTransform != null) {
|
|
554
|
+
runProperties.textTransform = String(attrs.textTransform);
|
|
555
|
+
}
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return runProperties;
|
|
561
|
+
}
|
|
562
|
+
function collectMarksRecursively(node, allMarks) {
|
|
563
|
+
if (node.type === "text" && node.marks && Array.isArray(node.marks)) {
|
|
564
|
+
allMarks.push(...node.marks);
|
|
565
|
+
}
|
|
566
|
+
if (node.content && Array.isArray(node.content)) {
|
|
567
|
+
for (const child of node.content) {
|
|
568
|
+
collectMarksRecursively(child, allMarks);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
function collectMarksFromRunChildren(runNode) {
|
|
573
|
+
const allMarks = [];
|
|
574
|
+
if (!runNode.content || !Array.isArray(runNode.content)) {
|
|
575
|
+
return allMarks;
|
|
576
|
+
}
|
|
577
|
+
for (const child of runNode.content) {
|
|
578
|
+
collectMarksRecursively(child, allMarks);
|
|
579
|
+
}
|
|
580
|
+
const marksByType = /* @__PURE__ */ new Map();
|
|
581
|
+
for (const mark of allMarks) {
|
|
582
|
+
marksByType.set(mark.type, mark);
|
|
583
|
+
}
|
|
584
|
+
return Array.from(marksByType.values());
|
|
585
|
+
}
|
|
586
|
+
function normalizeNode(node) {
|
|
587
|
+
if (node.type === "run") {
|
|
588
|
+
const marks = collectMarksFromRunChildren(node);
|
|
589
|
+
if (marks.length > 0) {
|
|
590
|
+
const runPropsFromMarks = marksToRunProperties(marks);
|
|
591
|
+
const existingRunProps = node.attrs?.runProperties || {};
|
|
592
|
+
const mergedRunProps = {
|
|
593
|
+
...existingRunProps,
|
|
594
|
+
...runPropsFromMarks
|
|
595
|
+
};
|
|
596
|
+
return {
|
|
597
|
+
...node,
|
|
598
|
+
attrs: {
|
|
599
|
+
...node.attrs,
|
|
600
|
+
runProperties: mergedRunProps
|
|
601
|
+
},
|
|
602
|
+
// Also recursively process children (though runs usually just have text)
|
|
603
|
+
content: node.content?.map(normalizeNode)
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (node.content && Array.isArray(node.content)) {
|
|
608
|
+
return {
|
|
609
|
+
...node,
|
|
610
|
+
content: node.content.map(normalizeNode)
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
return node;
|
|
614
|
+
}
|
|
615
|
+
function normalizeRunProperties(doc) {
|
|
616
|
+
const cloned = JSON.parse(JSON.stringify(doc));
|
|
617
|
+
return normalizeNode(cloned);
|
|
618
|
+
}
|
|
619
|
+
|
|
456
620
|
// src/services/contentResolver.ts
|
|
457
621
|
function detectContentType(content) {
|
|
458
622
|
if (content instanceof File) {
|
|
@@ -519,7 +683,8 @@ async function parseHtmlToJson(html, SuperDoc) {
|
|
|
519
683
|
try {
|
|
520
684
|
const json = editor.getJSON();
|
|
521
685
|
if (json?.content?.length > 0) {
|
|
522
|
-
|
|
686
|
+
const normalizedJson = normalizeRunProperties(json);
|
|
687
|
+
onSuccess(normalizedJson);
|
|
523
688
|
} else {
|
|
524
689
|
onFail();
|
|
525
690
|
}
|
|
@@ -556,9 +721,10 @@ async function parseHtmlToJson(html, SuperDoc) {
|
|
|
556
721
|
throw new Error("No active editor found");
|
|
557
722
|
}
|
|
558
723
|
const json = editor.getJSON();
|
|
724
|
+
const normalizedJson = normalizeRunProperties(json);
|
|
559
725
|
resolved = true;
|
|
560
726
|
cleanup();
|
|
561
|
-
resolve(
|
|
727
|
+
resolve(normalizedJson);
|
|
562
728
|
} catch (err) {
|
|
563
729
|
resolved = true;
|
|
564
730
|
cleanup();
|
|
@@ -836,14 +1002,20 @@ function diffDocuments(docA, docB) {
|
|
|
836
1002
|
const segments = [];
|
|
837
1003
|
let insertCount = 0;
|
|
838
1004
|
let deleteCount = 0;
|
|
1005
|
+
let posA = 0;
|
|
1006
|
+
let posB = 0;
|
|
839
1007
|
for (const [op, text] of diffs) {
|
|
840
1008
|
if (op === DIFF_EQUAL) {
|
|
841
|
-
segments.push({ type: "equal", text });
|
|
1009
|
+
segments.push({ type: "equal", text, posA, posB });
|
|
1010
|
+
posA += text.length;
|
|
1011
|
+
posB += text.length;
|
|
842
1012
|
} else if (op === DIFF_INSERT) {
|
|
843
|
-
segments.push({ type: "insert", text });
|
|
1013
|
+
segments.push({ type: "insert", text, posB });
|
|
1014
|
+
posB += text.length;
|
|
844
1015
|
insertCount++;
|
|
845
1016
|
} else if (op === DIFF_DELETE) {
|
|
846
|
-
segments.push({ type: "delete", text });
|
|
1017
|
+
segments.push({ type: "delete", text, posA });
|
|
1018
|
+
posA += text.length;
|
|
847
1019
|
deleteCount++;
|
|
848
1020
|
}
|
|
849
1021
|
}
|
|
@@ -868,7 +1040,9 @@ function diffDocuments(docA, docB) {
|
|
|
868
1040
|
formatChanges,
|
|
869
1041
|
textA,
|
|
870
1042
|
textB,
|
|
871
|
-
summary
|
|
1043
|
+
summary,
|
|
1044
|
+
spansB
|
|
1045
|
+
// Include docB spans for mark preservation during merge
|
|
872
1046
|
};
|
|
873
1047
|
}
|
|
874
1048
|
|
|
@@ -1350,6 +1524,69 @@ function alignListItems(listA, listB, listPathA, listPathB) {
|
|
|
1350
1524
|
function cloneNode(node) {
|
|
1351
1525
|
return JSON.parse(JSON.stringify(node));
|
|
1352
1526
|
}
|
|
1527
|
+
function getMarkSpansForRange(spansB, start, end) {
|
|
1528
|
+
const result = [];
|
|
1529
|
+
for (const span of spansB) {
|
|
1530
|
+
if (span.to > start && span.from < end) {
|
|
1531
|
+
const overlapStart = Math.max(span.from, start);
|
|
1532
|
+
const overlapEnd = Math.min(span.to, end);
|
|
1533
|
+
result.push({
|
|
1534
|
+
relStart: overlapStart - start,
|
|
1535
|
+
relEnd: overlapEnd - start,
|
|
1536
|
+
marks: span.marks || []
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
return result;
|
|
1541
|
+
}
|
|
1542
|
+
function createInsertedTextNodes(text, posB, spansB, author, replacementId) {
|
|
1543
|
+
const result = [];
|
|
1544
|
+
const trackMark = createTrackInsertMark(author, replacementId);
|
|
1545
|
+
if (posB === void 0 || spansB.length === 0) {
|
|
1546
|
+
return [{
|
|
1547
|
+
type: "text",
|
|
1548
|
+
text,
|
|
1549
|
+
marks: [trackMark]
|
|
1550
|
+
}];
|
|
1551
|
+
}
|
|
1552
|
+
const markSpans = getMarkSpansForRange(spansB, posB, posB + text.length);
|
|
1553
|
+
if (markSpans.length === 0) {
|
|
1554
|
+
return [{
|
|
1555
|
+
type: "text",
|
|
1556
|
+
text,
|
|
1557
|
+
marks: [trackMark]
|
|
1558
|
+
}];
|
|
1559
|
+
}
|
|
1560
|
+
markSpans.sort((a, b) => a.relStart - b.relStart);
|
|
1561
|
+
let processedUpTo = 0;
|
|
1562
|
+
for (const span of markSpans) {
|
|
1563
|
+
if (span.relStart > processedUpTo) {
|
|
1564
|
+
result.push({
|
|
1565
|
+
type: "text",
|
|
1566
|
+
text: text.substring(processedUpTo, span.relStart),
|
|
1567
|
+
marks: [trackMark]
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
if (span.relEnd > span.relStart) {
|
|
1571
|
+
const spanText = text.substring(span.relStart, span.relEnd);
|
|
1572
|
+
const marks = [...span.marks, trackMark];
|
|
1573
|
+
result.push({
|
|
1574
|
+
type: "text",
|
|
1575
|
+
text: spanText,
|
|
1576
|
+
marks
|
|
1577
|
+
});
|
|
1578
|
+
processedUpTo = span.relEnd;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
if (processedUpTo < text.length) {
|
|
1582
|
+
result.push({
|
|
1583
|
+
type: "text",
|
|
1584
|
+
text: text.substring(processedUpTo),
|
|
1585
|
+
marks: [trackMark]
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
return result;
|
|
1589
|
+
}
|
|
1353
1590
|
function mergeDocuments(docA, docB, diffResult, author = DEFAULT_AUTHOR) {
|
|
1354
1591
|
const merged = cloneNode(docA);
|
|
1355
1592
|
const charStates = [];
|
|
@@ -1384,17 +1621,22 @@ function mergeDocuments(docA, docB, diffResult, author = DEFAULT_AUTHOR) {
|
|
|
1384
1621
|
insertions.push({
|
|
1385
1622
|
afterOffset: docAOffset,
|
|
1386
1623
|
text: nextSegment.text,
|
|
1387
|
-
replacementId
|
|
1624
|
+
replacementId,
|
|
1625
|
+
posB: nextSegment.posB
|
|
1626
|
+
// Capture docB position for mark lookup
|
|
1388
1627
|
});
|
|
1389
1628
|
segIdx++;
|
|
1390
1629
|
}
|
|
1391
1630
|
} else if (segment.type === "insert") {
|
|
1392
1631
|
insertions.push({
|
|
1393
1632
|
afterOffset: docAOffset,
|
|
1394
|
-
text: segment.text
|
|
1633
|
+
text: segment.text,
|
|
1634
|
+
posB: segment.posB
|
|
1635
|
+
// Capture docB position for mark lookup
|
|
1395
1636
|
});
|
|
1396
1637
|
}
|
|
1397
1638
|
}
|
|
1639
|
+
const spansB = diffResult.spansB || [];
|
|
1398
1640
|
function transformNode(node, nodeOffset, path) {
|
|
1399
1641
|
if (node.type === "text" && node.text) {
|
|
1400
1642
|
const text = node.text;
|
|
@@ -1405,11 +1647,14 @@ function mergeDocuments(docA, docB, diffResult, author = DEFAULT_AUTHOR) {
|
|
|
1405
1647
|
const charState = charStates[charOffset] || { type: "equal" };
|
|
1406
1648
|
const insertionsHere = insertions.filter((ins) => ins.afterOffset === charOffset);
|
|
1407
1649
|
for (const ins of insertionsHere) {
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1650
|
+
const insertedNodes = createInsertedTextNodes(
|
|
1651
|
+
ins.text,
|
|
1652
|
+
ins.posB,
|
|
1653
|
+
spansB,
|
|
1654
|
+
author,
|
|
1655
|
+
ins.replacementId
|
|
1656
|
+
);
|
|
1657
|
+
result.push(...insertedNodes);
|
|
1413
1658
|
}
|
|
1414
1659
|
const currentFormatChange = getFormatChangeAt(nodeOffset + i);
|
|
1415
1660
|
let j = i + 1;
|
|
@@ -1445,11 +1690,14 @@ function mergeDocuments(docA, docB, diffResult, author = DEFAULT_AUTHOR) {
|
|
|
1445
1690
|
const endOffset = nodeOffset + text.length;
|
|
1446
1691
|
const endInsertions = insertions.filter((ins) => ins.afterOffset === endOffset);
|
|
1447
1692
|
for (const ins of endInsertions) {
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1693
|
+
const insertedNodes = createInsertedTextNodes(
|
|
1694
|
+
ins.text,
|
|
1695
|
+
ins.posB,
|
|
1696
|
+
spansB,
|
|
1697
|
+
author,
|
|
1698
|
+
ins.replacementId
|
|
1699
|
+
);
|
|
1700
|
+
result.push(...insertedNodes);
|
|
1453
1701
|
}
|
|
1454
1702
|
insertions = insertions.filter(
|
|
1455
1703
|
(ins) => ins.afterOffset < nodeOffset || ins.afterOffset > endOffset
|
|
@@ -1484,18 +1732,19 @@ function mergeDocuments(docA, docB, diffResult, author = DEFAULT_AUTHOR) {
|
|
|
1484
1732
|
}
|
|
1485
1733
|
if (insertions.length > 0) {
|
|
1486
1734
|
for (const ins of insertions) {
|
|
1735
|
+
const insertedNodes = createInsertedTextNodes(
|
|
1736
|
+
ins.text,
|
|
1737
|
+
ins.posB,
|
|
1738
|
+
spansB,
|
|
1739
|
+
author,
|
|
1740
|
+
ins.replacementId
|
|
1741
|
+
);
|
|
1487
1742
|
const insertNode = {
|
|
1488
1743
|
type: "paragraph",
|
|
1489
1744
|
content: [
|
|
1490
1745
|
{
|
|
1491
1746
|
type: "run",
|
|
1492
|
-
content:
|
|
1493
|
-
{
|
|
1494
|
-
type: "text",
|
|
1495
|
-
text: ins.text,
|
|
1496
|
-
marks: [createTrackInsertMark(author, ins.replacementId)]
|
|
1497
|
-
}
|
|
1498
|
-
]
|
|
1747
|
+
content: insertedNodes
|
|
1499
1748
|
}
|
|
1500
1749
|
]
|
|
1501
1750
|
};
|
|
@@ -2861,9 +3110,10 @@ var DocxDiffEditor = react.forwardRef(
|
|
|
2861
3110
|
}
|
|
2862
3111
|
newJson = content;
|
|
2863
3112
|
}
|
|
3113
|
+
const normalizedNewJson = normalizeRunProperties(newJson);
|
|
2864
3114
|
const structuralResult = mergeWithStructuralAwareness(
|
|
2865
3115
|
sourceJson,
|
|
2866
|
-
|
|
3116
|
+
normalizedNewJson,
|
|
2867
3117
|
author
|
|
2868
3118
|
);
|
|
2869
3119
|
const merged = structuralResult.mergedDoc;
|