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.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
- try {
500
- const editor = sd?.activeEditor;
501
- if (!editor) {
502
- throw new Error("No active editor found");
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
- try {
514
- const json = editor.getJSON();
515
- resolved = true;
516
- cleanup();
517
- resolve(json);
518
- } catch (err) {
519
- resolved = true;
520
- cleanup();
521
- reject(err);
522
- }
523
- }, 50);
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
- resolved = true;
533
- cleanup();
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
- cleanup();
546
- reject(err);
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
- newJson,
3025
+ normalizedNewJson,
2787
3026
  author
2788
3027
  );
2789
3028
  const merged = structuralResult.mergedDoc;