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.d.mts
CHANGED
|
@@ -436,6 +436,8 @@ declare function isProseMirrorJSON(content: unknown): boolean;
|
|
|
436
436
|
* 3. Select all content and delete it (start fresh)
|
|
437
437
|
* 4. Use editor.view.pasteHTML(html) - this uses the paste path which preserves styles
|
|
438
438
|
* 5. Return the resulting JSON
|
|
439
|
+
*
|
|
440
|
+
* Falls back to the standard import approach if paste fails.
|
|
439
441
|
*/
|
|
440
442
|
declare function parseHtmlToJson(html: string, SuperDoc: SuperDocConstructor): Promise<ProseMirrorJSON>;
|
|
441
443
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -436,6 +436,8 @@ declare function isProseMirrorJSON(content: unknown): boolean;
|
|
|
436
436
|
* 3. Select all content and delete it (start fresh)
|
|
437
437
|
* 4. Use editor.view.pasteHTML(html) - this uses the paste path which preserves styles
|
|
438
438
|
* 5. Return the resulting JSON
|
|
439
|
+
*
|
|
440
|
+
* Falls back to the standard import approach if paste fails.
|
|
439
441
|
*/
|
|
440
442
|
declare function parseHtmlToJson(html: string, SuperDoc: SuperDocConstructor): Promise<ProseMirrorJSON>;
|
|
441
443
|
/**
|
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) {
|
|
@@ -490,6 +654,91 @@ async function parseHtmlToJson(html, SuperDoc) {
|
|
|
490
654
|
}
|
|
491
655
|
}, TIMEOUTS.CLEANUP_DELAY);
|
|
492
656
|
};
|
|
657
|
+
const createMockPasteEvent = (htmlContent) => {
|
|
658
|
+
const dataTransfer = new DataTransfer();
|
|
659
|
+
dataTransfer.setData("text/html", htmlContent);
|
|
660
|
+
dataTransfer.setData("text/plain", "");
|
|
661
|
+
const event = new ClipboardEvent("paste", {
|
|
662
|
+
bubbles: true,
|
|
663
|
+
cancelable: true,
|
|
664
|
+
clipboardData: dataTransfer
|
|
665
|
+
});
|
|
666
|
+
return event;
|
|
667
|
+
};
|
|
668
|
+
const tryPasteApproach = (sd, onSuccess, onFail) => {
|
|
669
|
+
try {
|
|
670
|
+
const editor = sd?.activeEditor;
|
|
671
|
+
if (!editor?.view?.pasteHTML) {
|
|
672
|
+
onFail();
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
editor.commands.focus?.();
|
|
676
|
+
if (editor.commands.selectAll && editor.commands.deleteSelection) {
|
|
677
|
+
editor.commands.selectAll();
|
|
678
|
+
editor.commands.deleteSelection();
|
|
679
|
+
}
|
|
680
|
+
const mockEvent = createMockPasteEvent(html);
|
|
681
|
+
editor.view.pasteHTML(html, mockEvent);
|
|
682
|
+
setTimeout(() => {
|
|
683
|
+
try {
|
|
684
|
+
const json = editor.getJSON();
|
|
685
|
+
if (json?.content?.length > 0) {
|
|
686
|
+
const normalizedJson = normalizeRunProperties(json);
|
|
687
|
+
onSuccess(normalizedJson);
|
|
688
|
+
} else {
|
|
689
|
+
onFail();
|
|
690
|
+
}
|
|
691
|
+
} catch {
|
|
692
|
+
onFail();
|
|
693
|
+
}
|
|
694
|
+
}, 100);
|
|
695
|
+
} catch (err) {
|
|
696
|
+
console.warn("[parseHtmlToJson] Paste approach error:", err);
|
|
697
|
+
onFail();
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
const fallbackToImport = () => {
|
|
701
|
+
if (superdoc) {
|
|
702
|
+
try {
|
|
703
|
+
superdoc.destroy?.();
|
|
704
|
+
} catch {
|
|
705
|
+
}
|
|
706
|
+
superdoc = null;
|
|
707
|
+
}
|
|
708
|
+
superdoc = new SuperDoc({
|
|
709
|
+
selector: container,
|
|
710
|
+
html,
|
|
711
|
+
// Use the actual HTML content
|
|
712
|
+
documentMode: "viewing",
|
|
713
|
+
rulers: false,
|
|
714
|
+
user: { name: "Parser", email: "parser@local" },
|
|
715
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
716
|
+
onReady: ({ superdoc: sd }) => {
|
|
717
|
+
if (resolved) return;
|
|
718
|
+
try {
|
|
719
|
+
const editor = sd?.activeEditor;
|
|
720
|
+
if (!editor) {
|
|
721
|
+
throw new Error("No active editor found");
|
|
722
|
+
}
|
|
723
|
+
const json = editor.getJSON();
|
|
724
|
+
const normalizedJson = normalizeRunProperties(json);
|
|
725
|
+
resolved = true;
|
|
726
|
+
cleanup();
|
|
727
|
+
resolve(normalizedJson);
|
|
728
|
+
} catch (err) {
|
|
729
|
+
resolved = true;
|
|
730
|
+
cleanup();
|
|
731
|
+
reject(err);
|
|
732
|
+
}
|
|
733
|
+
},
|
|
734
|
+
onException: ({ error: err }) => {
|
|
735
|
+
if (resolved) return;
|
|
736
|
+
resolved = true;
|
|
737
|
+
cleanup();
|
|
738
|
+
reject(err);
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
};
|
|
493
742
|
setTimeout(async () => {
|
|
494
743
|
if (resolved) return;
|
|
495
744
|
try {
|
|
@@ -504,42 +753,27 @@ async function parseHtmlToJson(html, SuperDoc) {
|
|
|
504
753
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
505
754
|
onReady: ({ superdoc: sd }) => {
|
|
506
755
|
if (resolved) return;
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}
|
|
512
|
-
const view = editor.view;
|
|
513
|
-
if (!view) {
|
|
514
|
-
throw new Error("No editor view found");
|
|
515
|
-
}
|
|
516
|
-
editor.commands.selectAll();
|
|
517
|
-
editor.commands.deleteSelection();
|
|
518
|
-
view.pasteHTML(html);
|
|
519
|
-
setTimeout(() => {
|
|
756
|
+
tryPasteApproach(
|
|
757
|
+
sd,
|
|
758
|
+
// Success callback
|
|
759
|
+
(json) => {
|
|
520
760
|
if (resolved) return;
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
} catch (err) {
|
|
533
|
-
resolved = true;
|
|
534
|
-
cleanup();
|
|
535
|
-
reject(err);
|
|
536
|
-
}
|
|
761
|
+
resolved = true;
|
|
762
|
+
cleanup();
|
|
763
|
+
resolve(json);
|
|
764
|
+
},
|
|
765
|
+
// Fail callback - try fallback
|
|
766
|
+
() => {
|
|
767
|
+
if (resolved) return;
|
|
768
|
+
console.warn("[parseHtmlToJson] Paste approach failed, falling back to import");
|
|
769
|
+
fallbackToImport();
|
|
770
|
+
}
|
|
771
|
+
);
|
|
537
772
|
},
|
|
538
773
|
onException: ({ error: err }) => {
|
|
539
774
|
if (resolved) return;
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
reject(err);
|
|
775
|
+
console.warn("[parseHtmlToJson] Paste approach exception, falling back:", err);
|
|
776
|
+
fallbackToImport();
|
|
543
777
|
}
|
|
544
778
|
});
|
|
545
779
|
setTimeout(() => {
|
|
@@ -550,8 +784,12 @@ async function parseHtmlToJson(html, SuperDoc) {
|
|
|
550
784
|
}
|
|
551
785
|
}, TIMEOUTS.PARSE_TIMEOUT);
|
|
552
786
|
} catch (err) {
|
|
553
|
-
|
|
554
|
-
|
|
787
|
+
try {
|
|
788
|
+
fallbackToImport();
|
|
789
|
+
} catch (fallbackErr) {
|
|
790
|
+
cleanup();
|
|
791
|
+
reject(fallbackErr);
|
|
792
|
+
}
|
|
555
793
|
}
|
|
556
794
|
}, 50);
|
|
557
795
|
});
|
|
@@ -2789,9 +3027,10 @@ var DocxDiffEditor = react.forwardRef(
|
|
|
2789
3027
|
}
|
|
2790
3028
|
newJson = content;
|
|
2791
3029
|
}
|
|
3030
|
+
const normalizedNewJson = normalizeRunProperties(newJson);
|
|
2792
3031
|
const structuralResult = mergeWithStructuralAwareness(
|
|
2793
3032
|
sourceJson,
|
|
2794
|
-
|
|
3033
|
+
normalizedNewJson,
|
|
2795
3034
|
author
|
|
2796
3035
|
);
|
|
2797
3036
|
const merged = structuralResult.mergedDoc;
|