docx-diff-editor 1.0.44 → 1.0.46
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 +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +274 -35
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +274 -35
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -445,6 +445,170 @@ var TIMEOUTS = {
|
|
|
445
445
|
CLEANUP_DELAY: 100
|
|
446
446
|
};
|
|
447
447
|
|
|
448
|
+
// src/services/runPropertiesSync.ts
|
|
449
|
+
var PT_TO_TWIPS = 20;
|
|
450
|
+
function ptToTwips(ptValue) {
|
|
451
|
+
return Math.round(ptValue * PT_TO_TWIPS);
|
|
452
|
+
}
|
|
453
|
+
function stripHashFromColor(color) {
|
|
454
|
+
return color.replace(/^#/, "");
|
|
455
|
+
}
|
|
456
|
+
function parseFontSizeToPoints(fontSize) {
|
|
457
|
+
if (typeof fontSize === "number") {
|
|
458
|
+
return fontSize;
|
|
459
|
+
}
|
|
460
|
+
const value = parseFloat(fontSize);
|
|
461
|
+
if (isNaN(value)) {
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
if (fontSize.toLowerCase().includes("px")) {
|
|
465
|
+
return value * 0.75;
|
|
466
|
+
}
|
|
467
|
+
return value;
|
|
468
|
+
}
|
|
469
|
+
function cleanFontFamily(fontFamily) {
|
|
470
|
+
return fontFamily.split(",")[0].trim().replace(/^["']|["']$/g, "");
|
|
471
|
+
}
|
|
472
|
+
function marksToRunProperties(marks) {
|
|
473
|
+
const runProperties = {};
|
|
474
|
+
if (!marks || !Array.isArray(marks)) {
|
|
475
|
+
return runProperties;
|
|
476
|
+
}
|
|
477
|
+
for (const mark of marks) {
|
|
478
|
+
const type = mark.type;
|
|
479
|
+
const attrs = mark.attrs || {};
|
|
480
|
+
switch (type) {
|
|
481
|
+
// Boolean marks: bold, italic, strike
|
|
482
|
+
case "bold":
|
|
483
|
+
case "italic":
|
|
484
|
+
case "strike": {
|
|
485
|
+
const isNegated = attrs.value === "0" || attrs.value === false;
|
|
486
|
+
runProperties[type] = !isNegated;
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
// Underline with optional type and color
|
|
490
|
+
case "underline": {
|
|
491
|
+
const underlineAttrs = {};
|
|
492
|
+
if (attrs.underlineType) {
|
|
493
|
+
underlineAttrs["w:val"] = String(attrs.underlineType);
|
|
494
|
+
} else {
|
|
495
|
+
underlineAttrs["w:val"] = "single";
|
|
496
|
+
}
|
|
497
|
+
if (attrs.underlineColor) {
|
|
498
|
+
underlineAttrs["w:color"] = stripHashFromColor(String(attrs.underlineColor));
|
|
499
|
+
}
|
|
500
|
+
if (Object.keys(underlineAttrs).length > 0) {
|
|
501
|
+
runProperties.underline = underlineAttrs;
|
|
502
|
+
}
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
// Highlight (background color)
|
|
506
|
+
case "highlight": {
|
|
507
|
+
if (attrs.color) {
|
|
508
|
+
const color = String(attrs.color).toLowerCase();
|
|
509
|
+
if (color === "transparent") {
|
|
510
|
+
runProperties.highlight = { "w:val": "none" };
|
|
511
|
+
} else {
|
|
512
|
+
runProperties.highlight = { "w:val": color };
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
// textStyle contains multiple style attributes
|
|
518
|
+
case "textStyle": {
|
|
519
|
+
if (attrs.color != null) {
|
|
520
|
+
runProperties.color = {
|
|
521
|
+
val: stripHashFromColor(String(attrs.color))
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
if (attrs.fontSize != null) {
|
|
525
|
+
const points = parseFontSizeToPoints(attrs.fontSize);
|
|
526
|
+
if (points !== null) {
|
|
527
|
+
runProperties.fontSize = points * 2;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (attrs.fontFamily != null) {
|
|
531
|
+
const cleanedFont = cleanFontFamily(String(attrs.fontFamily));
|
|
532
|
+
runProperties.fontFamily = {
|
|
533
|
+
ascii: cleanedFont,
|
|
534
|
+
eastAsia: cleanedFont,
|
|
535
|
+
hAnsi: cleanedFont,
|
|
536
|
+
cs: cleanedFont
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
if (attrs.letterSpacing != null) {
|
|
540
|
+
const ptValue = parseFloat(String(attrs.letterSpacing));
|
|
541
|
+
if (!isNaN(ptValue)) {
|
|
542
|
+
runProperties.letterSpacing = ptToTwips(ptValue);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (attrs.textTransform != null) {
|
|
546
|
+
runProperties.textTransform = String(attrs.textTransform);
|
|
547
|
+
}
|
|
548
|
+
break;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return runProperties;
|
|
553
|
+
}
|
|
554
|
+
function collectMarksRecursively(node, allMarks) {
|
|
555
|
+
if (node.type === "text" && node.marks && Array.isArray(node.marks)) {
|
|
556
|
+
allMarks.push(...node.marks);
|
|
557
|
+
}
|
|
558
|
+
if (node.content && Array.isArray(node.content)) {
|
|
559
|
+
for (const child of node.content) {
|
|
560
|
+
collectMarksRecursively(child, allMarks);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function collectMarksFromRunChildren(runNode) {
|
|
565
|
+
const allMarks = [];
|
|
566
|
+
if (!runNode.content || !Array.isArray(runNode.content)) {
|
|
567
|
+
return allMarks;
|
|
568
|
+
}
|
|
569
|
+
for (const child of runNode.content) {
|
|
570
|
+
collectMarksRecursively(child, allMarks);
|
|
571
|
+
}
|
|
572
|
+
const marksByType = /* @__PURE__ */ new Map();
|
|
573
|
+
for (const mark of allMarks) {
|
|
574
|
+
marksByType.set(mark.type, mark);
|
|
575
|
+
}
|
|
576
|
+
return Array.from(marksByType.values());
|
|
577
|
+
}
|
|
578
|
+
function normalizeNode(node) {
|
|
579
|
+
if (node.type === "run") {
|
|
580
|
+
const marks = collectMarksFromRunChildren(node);
|
|
581
|
+
if (marks.length > 0) {
|
|
582
|
+
const runPropsFromMarks = marksToRunProperties(marks);
|
|
583
|
+
const existingRunProps = node.attrs?.runProperties || {};
|
|
584
|
+
const mergedRunProps = {
|
|
585
|
+
...existingRunProps,
|
|
586
|
+
...runPropsFromMarks
|
|
587
|
+
};
|
|
588
|
+
return {
|
|
589
|
+
...node,
|
|
590
|
+
attrs: {
|
|
591
|
+
...node.attrs,
|
|
592
|
+
runProperties: mergedRunProps
|
|
593
|
+
},
|
|
594
|
+
// Also recursively process children (though runs usually just have text)
|
|
595
|
+
content: node.content?.map(normalizeNode)
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (node.content && Array.isArray(node.content)) {
|
|
600
|
+
return {
|
|
601
|
+
...node,
|
|
602
|
+
content: node.content.map(normalizeNode)
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
return node;
|
|
606
|
+
}
|
|
607
|
+
function normalizeRunProperties(doc) {
|
|
608
|
+
const cloned = JSON.parse(JSON.stringify(doc));
|
|
609
|
+
return normalizeNode(cloned);
|
|
610
|
+
}
|
|
611
|
+
|
|
448
612
|
// src/services/contentResolver.ts
|
|
449
613
|
function detectContentType(content) {
|
|
450
614
|
if (content instanceof File) {
|
|
@@ -482,6 +646,91 @@ async function parseHtmlToJson(html, SuperDoc) {
|
|
|
482
646
|
}
|
|
483
647
|
}, TIMEOUTS.CLEANUP_DELAY);
|
|
484
648
|
};
|
|
649
|
+
const createMockPasteEvent = (htmlContent) => {
|
|
650
|
+
const dataTransfer = new DataTransfer();
|
|
651
|
+
dataTransfer.setData("text/html", htmlContent);
|
|
652
|
+
dataTransfer.setData("text/plain", "");
|
|
653
|
+
const event = new ClipboardEvent("paste", {
|
|
654
|
+
bubbles: true,
|
|
655
|
+
cancelable: true,
|
|
656
|
+
clipboardData: dataTransfer
|
|
657
|
+
});
|
|
658
|
+
return event;
|
|
659
|
+
};
|
|
660
|
+
const tryPasteApproach = (sd, onSuccess, onFail) => {
|
|
661
|
+
try {
|
|
662
|
+
const editor = sd?.activeEditor;
|
|
663
|
+
if (!editor?.view?.pasteHTML) {
|
|
664
|
+
onFail();
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
editor.commands.focus?.();
|
|
668
|
+
if (editor.commands.selectAll && editor.commands.deleteSelection) {
|
|
669
|
+
editor.commands.selectAll();
|
|
670
|
+
editor.commands.deleteSelection();
|
|
671
|
+
}
|
|
672
|
+
const mockEvent = createMockPasteEvent(html);
|
|
673
|
+
editor.view.pasteHTML(html, mockEvent);
|
|
674
|
+
setTimeout(() => {
|
|
675
|
+
try {
|
|
676
|
+
const json = editor.getJSON();
|
|
677
|
+
if (json?.content?.length > 0) {
|
|
678
|
+
const normalizedJson = normalizeRunProperties(json);
|
|
679
|
+
onSuccess(normalizedJson);
|
|
680
|
+
} else {
|
|
681
|
+
onFail();
|
|
682
|
+
}
|
|
683
|
+
} catch {
|
|
684
|
+
onFail();
|
|
685
|
+
}
|
|
686
|
+
}, 100);
|
|
687
|
+
} catch (err) {
|
|
688
|
+
console.warn("[parseHtmlToJson] Paste approach error:", err);
|
|
689
|
+
onFail();
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
const fallbackToImport = () => {
|
|
693
|
+
if (superdoc) {
|
|
694
|
+
try {
|
|
695
|
+
superdoc.destroy?.();
|
|
696
|
+
} catch {
|
|
697
|
+
}
|
|
698
|
+
superdoc = null;
|
|
699
|
+
}
|
|
700
|
+
superdoc = new SuperDoc({
|
|
701
|
+
selector: container,
|
|
702
|
+
html,
|
|
703
|
+
// Use the actual HTML content
|
|
704
|
+
documentMode: "viewing",
|
|
705
|
+
rulers: false,
|
|
706
|
+
user: { name: "Parser", email: "parser@local" },
|
|
707
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
708
|
+
onReady: ({ superdoc: sd }) => {
|
|
709
|
+
if (resolved) return;
|
|
710
|
+
try {
|
|
711
|
+
const editor = sd?.activeEditor;
|
|
712
|
+
if (!editor) {
|
|
713
|
+
throw new Error("No active editor found");
|
|
714
|
+
}
|
|
715
|
+
const json = editor.getJSON();
|
|
716
|
+
const normalizedJson = normalizeRunProperties(json);
|
|
717
|
+
resolved = true;
|
|
718
|
+
cleanup();
|
|
719
|
+
resolve(normalizedJson);
|
|
720
|
+
} catch (err) {
|
|
721
|
+
resolved = true;
|
|
722
|
+
cleanup();
|
|
723
|
+
reject(err);
|
|
724
|
+
}
|
|
725
|
+
},
|
|
726
|
+
onException: ({ error: err }) => {
|
|
727
|
+
if (resolved) return;
|
|
728
|
+
resolved = true;
|
|
729
|
+
cleanup();
|
|
730
|
+
reject(err);
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
};
|
|
485
734
|
setTimeout(async () => {
|
|
486
735
|
if (resolved) return;
|
|
487
736
|
try {
|
|
@@ -496,42 +745,27 @@ async function parseHtmlToJson(html, SuperDoc) {
|
|
|
496
745
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
497
746
|
onReady: ({ superdoc: sd }) => {
|
|
498
747
|
if (resolved) return;
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
}
|
|
504
|
-
const view = editor.view;
|
|
505
|
-
if (!view) {
|
|
506
|
-
throw new Error("No editor view found");
|
|
507
|
-
}
|
|
508
|
-
editor.commands.selectAll();
|
|
509
|
-
editor.commands.deleteSelection();
|
|
510
|
-
view.pasteHTML(html);
|
|
511
|
-
setTimeout(() => {
|
|
748
|
+
tryPasteApproach(
|
|
749
|
+
sd,
|
|
750
|
+
// Success callback
|
|
751
|
+
(json) => {
|
|
512
752
|
if (resolved) return;
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
} catch (err) {
|
|
525
|
-
resolved = true;
|
|
526
|
-
cleanup();
|
|
527
|
-
reject(err);
|
|
528
|
-
}
|
|
753
|
+
resolved = true;
|
|
754
|
+
cleanup();
|
|
755
|
+
resolve(json);
|
|
756
|
+
},
|
|
757
|
+
// Fail callback - try fallback
|
|
758
|
+
() => {
|
|
759
|
+
if (resolved) return;
|
|
760
|
+
console.warn("[parseHtmlToJson] Paste approach failed, falling back to import");
|
|
761
|
+
fallbackToImport();
|
|
762
|
+
}
|
|
763
|
+
);
|
|
529
764
|
},
|
|
530
765
|
onException: ({ error: err }) => {
|
|
531
766
|
if (resolved) return;
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
reject(err);
|
|
767
|
+
console.warn("[parseHtmlToJson] Paste approach exception, falling back:", err);
|
|
768
|
+
fallbackToImport();
|
|
535
769
|
}
|
|
536
770
|
});
|
|
537
771
|
setTimeout(() => {
|
|
@@ -542,8 +776,12 @@ async function parseHtmlToJson(html, SuperDoc) {
|
|
|
542
776
|
}
|
|
543
777
|
}, TIMEOUTS.PARSE_TIMEOUT);
|
|
544
778
|
} catch (err) {
|
|
545
|
-
|
|
546
|
-
|
|
779
|
+
try {
|
|
780
|
+
fallbackToImport();
|
|
781
|
+
} catch (fallbackErr) {
|
|
782
|
+
cleanup();
|
|
783
|
+
reject(fallbackErr);
|
|
784
|
+
}
|
|
547
785
|
}
|
|
548
786
|
}, 50);
|
|
549
787
|
});
|
|
@@ -2781,9 +3019,10 @@ var DocxDiffEditor = forwardRef(
|
|
|
2781
3019
|
}
|
|
2782
3020
|
newJson = content;
|
|
2783
3021
|
}
|
|
3022
|
+
const normalizedNewJson = normalizeRunProperties(newJson);
|
|
2784
3023
|
const structuralResult = mergeWithStructuralAwareness(
|
|
2785
3024
|
sourceJson,
|
|
2786
|
-
|
|
3025
|
+
normalizedNewJson,
|
|
2787
3026
|
author
|
|
2788
3027
|
);
|
|
2789
3028
|
const merged = structuralResult.mergedDoc;
|