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 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
- try {
508
- const editor = sd?.activeEditor;
509
- if (!editor) {
510
- throw new Error("No active editor found");
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
- try {
522
- const json = editor.getJSON();
523
- resolved = true;
524
- cleanup();
525
- resolve(json);
526
- } catch (err) {
527
- resolved = true;
528
- cleanup();
529
- reject(err);
530
- }
531
- }, 50);
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
- resolved = true;
541
- cleanup();
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
- cleanup();
554
- reject(err);
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
- newJson,
3033
+ normalizedNewJson,
2795
3034
  author
2796
3035
  );
2797
3036
  const merged = structuralResult.mergedDoc;