remark-docx 0.1.7 → 0.1.8

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/README.md CHANGED
@@ -23,7 +23,7 @@ If you have some feature requests or improvements, please create a [issue](https
23
23
  - [ ] code
24
24
  - [ ] yaml
25
25
  - [ ] toml
26
- - [ ] definition
26
+ - [x] definition
27
27
  - [x] footnoteDefinition
28
28
  - [x] text
29
29
  - [x] emphasis
@@ -33,8 +33,8 @@ If you have some feature requests or improvements, please create a [issue](https
33
33
  - [x] break
34
34
  - [x] link
35
35
  - [x] image
36
- - [ ] linkReference
37
- - [ ] imageReference
36
+ - [x] linkReference
37
+ - [x] imageReference
38
38
  - [x] footnote
39
39
  - [x] footnoteReference
40
40
  - [x] LaTeX support with math and inlineMath ([remark-math](https://github.com/remarkjs/remark-math) is required)
package/lib/index.js CHANGED
@@ -427,13 +427,44 @@ const DEFAULT_NUMBERINGS = [
427
427
  },
428
428
  },
429
429
  ];
430
+ const createFootnoteRegistry = () => {
431
+ const idToInternalId = new Map();
432
+ const defs = new Map();
433
+ const getId = (id) => {
434
+ let internalId = idToInternalId.get(id);
435
+ if (internalId == null) {
436
+ idToInternalId.set(id, (internalId = idToInternalId.size + 1));
437
+ }
438
+ return internalId;
439
+ };
440
+ return {
441
+ ref: (id) => {
442
+ return getId(id);
443
+ },
444
+ def: (id, def) => {
445
+ const internalId = getId(id);
446
+ defs.set(internalId, def);
447
+ },
448
+ footnotes: () => {
449
+ return defs.entries().reduce((acc, [key, def]) => {
450
+ acc[key] = def;
451
+ return acc;
452
+ }, {});
453
+ },
454
+ };
455
+ };
430
456
  const mdastToDocx = async (node, { output = "buffer", title, subject, creator, keywords, description, lastModifiedBy, revision, styles, background, }, images) => {
431
- const footnoteRegistry = {};
432
- const { nodes, footnotes } = convertNodes(node.children, {
457
+ const definition = {};
458
+ unistUtilVisit.visit(node, "definition", (node) => {
459
+ definition[node.identifier] = node.url;
460
+ });
461
+ const footnote = createFootnoteRegistry();
462
+ const nodes = convertNodes(node.children, {
433
463
  deco: {},
434
464
  images,
435
465
  indent: 0,
436
- footnoteRegistry,
466
+ def: definition,
467
+ footnote,
437
468
  });
438
469
  const doc = new docx.Document({
439
470
  title,
@@ -445,7 +476,7 @@ const mdastToDocx = async (node, { output = "buffer", title, subject, creator, k
445
476
  revision,
446
477
  styles,
447
478
  background,
448
- footnotes,
479
+ footnotes: footnote.footnotes(),
449
480
  sections: [{ children: nodes }],
450
481
  numbering: {
451
482
  config: [
@@ -467,42 +498,27 @@ const mdastToDocx = async (node, { output = "buffer", title, subject, creator, k
467
498
  return docx.Packer.toBlob(doc);
468
499
  }
469
500
  };
470
- const getOrCreateFootnoteId = (identifier, registry) => {
471
- if (!(identifier in registry)) {
472
- registry[identifier] = Object.keys(registry).length + 1;
473
- }
474
- return registry[identifier];
475
- };
476
501
  const convertNodes = (nodes, ctx) => {
477
502
  const results = [];
478
- let footnotes = {};
479
503
  for (const node of nodes) {
480
504
  switch (node.type) {
481
505
  case "paragraph": {
482
- const { paragraph, footnotes: nestedFootnotes } = buildParagraph(node, ctx);
483
- results.push(paragraph);
484
- footnotes = { ...footnotes, ...nestedFootnotes };
506
+ results.push(buildParagraph(node, ctx));
485
507
  break;
486
508
  }
487
509
  case "heading": {
488
- const { heading, footnotes: nestedFootnotes } = buildHeading(node, ctx);
489
- results.push(heading);
490
- footnotes = { ...footnotes, ...nestedFootnotes };
510
+ results.push(buildHeading(node, ctx));
491
511
  break;
492
512
  }
493
513
  case "thematicBreak":
494
514
  results.push(buildThematicBreak());
495
515
  break;
496
516
  case "blockquote": {
497
- const { nodes: blockquoteNodes, footnotes: nestedFootnotes } = buildBlockquote(node, ctx);
498
- results.push(...blockquoteNodes);
499
- footnotes = { ...footnotes, ...nestedFootnotes };
517
+ results.push(...buildBlockquote(node, ctx));
500
518
  break;
501
519
  }
502
520
  case "list": {
503
- const { nodes: listNodes, footnotes: nestedFootnotes } = buildList(node, ctx);
504
- results.push(...listNodes);
505
- footnotes = { ...footnotes, ...nestedFootnotes };
521
+ results.push(...buildList(node, ctx));
506
522
  break;
507
523
  }
508
524
  case "listItem":
@@ -527,11 +543,10 @@ const convertNodes = (nodes, ctx) => {
527
543
  // FIXME: unimplemented
528
544
  break;
529
545
  case "definition":
530
- // FIXME: unimplemented
546
+ // noop
531
547
  break;
532
548
  case "footnoteDefinition": {
533
- const footnoteId = getOrCreateFootnoteId(node.identifier, ctx.footnoteRegistry || {});
534
- footnotes[footnoteId] = buildFootnoteDefinition(node, ctx);
549
+ registerFootnoteDefinition(node, ctx);
535
550
  break;
536
551
  }
537
552
  case "text":
@@ -541,12 +556,11 @@ const convertNodes = (nodes, ctx) => {
541
556
  case "strong":
542
557
  case "delete": {
543
558
  const { type, children } = node;
544
- const { nodes, footnotes: nestedFootnotes } = convertNodes(children, {
559
+ const nodes = convertNodes(children, {
545
560
  ...ctx,
546
561
  deco: { ...ctx.deco, [type]: true },
547
562
  });
548
563
  results.push(...nodes);
549
- footnotes = { ...footnotes, ...nestedFootnotes };
550
564
  break;
551
565
  }
552
566
  case "inlineCode":
@@ -557,24 +571,24 @@ const convertNodes = (nodes, ctx) => {
557
571
  results.push(buildBreak());
558
572
  break;
559
573
  case "link": {
560
- const { link, footnotes: nestedFootnotes } = buildLink(node, ctx);
561
- results.push(link);
562
- footnotes = { ...footnotes, ...nestedFootnotes };
574
+ results.push(buildLink(node, ctx));
563
575
  break;
564
576
  }
565
577
  case "image":
566
578
  results.push(buildImage(node, ctx.images));
567
579
  break;
568
580
  case "linkReference":
569
- // FIXME: unimplemented
581
+ results.push(...buildLinkReference(node, ctx));
570
582
  break;
571
- case "imageReference":
572
- // FIXME: unimplemented
583
+ case "imageReference": {
584
+ const image = buildImageReference(node, ctx);
585
+ if (image) {
586
+ results.push(image);
587
+ }
573
588
  break;
589
+ }
574
590
  case "footnote": {
575
- const { footnoteRef, footnoteData } = buildFootnote(node, ctx);
576
- results.push(footnoteRef);
577
- footnotes = { ...footnotes, ...footnoteData };
591
+ // inline footnote was removed in mdast v5
578
592
  break;
579
593
  }
580
594
  case "footnoteReference":
@@ -591,14 +605,11 @@ const convertNodes = (nodes, ctx) => {
591
605
  break;
592
606
  }
593
607
  }
594
- return {
595
- nodes: results,
596
- footnotes,
597
- };
608
+ return results;
598
609
  };
599
610
  const buildParagraph = ({ children }, ctx) => {
600
611
  const list = ctx.list;
601
- const { nodes, footnotes: nestedFootnotes } = convertNodes(children, ctx);
612
+ const nodes = convertNodes(children, ctx);
602
613
  if (list && list.checked != null) {
603
614
  nodes.unshift(new docx.CheckBox({
604
615
  checked: list.checked,
@@ -606,30 +617,27 @@ const buildParagraph = ({ children }, ctx) => {
606
617
  uncheckedState: { value: "2610" },
607
618
  }));
608
619
  }
609
- return {
610
- paragraph: new docx.Paragraph({
611
- children: nodes,
612
- indent: ctx.indent > 0
620
+ return new docx.Paragraph({
621
+ children: nodes,
622
+ indent: ctx.indent > 0
623
+ ? {
624
+ start: docx.convertInchesToTwip(INDENT * ctx.indent),
625
+ }
626
+ : undefined,
627
+ ...(list &&
628
+ (list.ordered
613
629
  ? {
614
- start: docx.convertInchesToTwip(INDENT * ctx.indent),
630
+ numbering: {
631
+ reference: ORDERED_LIST_REF,
632
+ level: list.level,
633
+ },
615
634
  }
616
- : undefined,
617
- ...(list &&
618
- (list.ordered
619
- ? {
620
- numbering: {
621
- reference: ORDERED_LIST_REF,
622
- level: list.level,
623
- },
624
- }
625
- : {
626
- bullet: {
627
- level: list.level,
628
- },
629
- })),
630
- }),
631
- footnotes: nestedFootnotes,
632
- };
635
+ : {
636
+ bullet: {
637
+ level: list.level,
638
+ },
639
+ })),
640
+ });
633
641
  };
634
642
  const buildHeading = ({ children, depth }, ctx) => {
635
643
  let headingLevel;
@@ -653,14 +661,11 @@ const buildHeading = ({ children, depth }, ctx) => {
653
661
  headingLevel = docx.HeadingLevel.HEADING_5;
654
662
  break;
655
663
  }
656
- const { nodes, footnotes: nestedFootnotes } = convertNodes(children, ctx);
657
- return {
658
- heading: new docx.Paragraph({
659
- heading: headingLevel,
660
- children: nodes,
661
- }),
662
- footnotes: nestedFootnotes,
663
- };
664
+ const nodes = convertNodes(children, ctx);
665
+ return new docx.Paragraph({
666
+ heading: headingLevel,
667
+ children: nodes,
668
+ });
664
669
  };
665
670
  const buildThematicBreak = (_) => {
666
671
  return new docx.Paragraph({
@@ -668,31 +673,28 @@ const buildThematicBreak = (_) => {
668
673
  });
669
674
  };
670
675
  const buildBlockquote = ({ children }, ctx) => {
671
- const { nodes, footnotes: nestedFootnotes } = convertNodes(children, { ...ctx, indent: ctx.indent + 1 });
672
- return { nodes, footnotes: nestedFootnotes };
676
+ return convertNodes(children, {
677
+ ...ctx,
678
+ indent: ctx.indent + 1,
679
+ });
673
680
  };
674
681
  const buildList = ({ children, ordered, start: _start, spread: _spread }, ctx) => {
675
682
  const list = {
676
683
  level: ctx.list ? ctx.list.level + 1 : 0,
677
684
  ordered: !!ordered,
678
685
  };
679
- let allFootnotes = {};
680
- const allNodes = children.flatMap((item) => {
681
- const { nodes, footnotes: itemFootnotes } = buildListItem(item, {
686
+ return children.flatMap((item) => {
687
+ return buildListItem(item, {
682
688
  ...ctx,
683
689
  list,
684
690
  });
685
- allFootnotes = { ...allFootnotes, ...itemFootnotes };
686
- return nodes;
687
691
  });
688
- return { nodes: allNodes, footnotes: allFootnotes };
689
692
  };
690
693
  const buildListItem = ({ children, checked, spread: _spread }, ctx) => {
691
- const { nodes, footnotes: nestedFootnotes } = convertNodes(children, {
694
+ return convertNodes(children, {
692
695
  ...ctx,
693
696
  ...(ctx.list && { list: { ...ctx.list, checked: checked !== null && checked !== void 0 ? checked : undefined } }),
694
697
  });
695
- return { nodes, footnotes: nestedFootnotes };
696
698
  };
697
699
  const buildTable = ({ children, align }, ctx) => {
698
700
  const cellAligns = align === null || align === void 0 ? void 0 : align.map((a) => {
@@ -721,7 +723,7 @@ const buildTableRow = ({ children }, ctx, cellAligns) => {
721
723
  });
722
724
  };
723
725
  const buildTableCell = ({ children }, ctx, align) => {
724
- const { nodes } = convertNodes(children, ctx);
726
+ const nodes = convertNodes(children, ctx);
725
727
  return new docx.TableCell({
726
728
  children: [
727
729
  new docx.Paragraph({
@@ -737,7 +739,7 @@ const buildHtml = ({ value }) => {
737
739
  children: [buildText(value, {})],
738
740
  });
739
741
  };
740
- const buildCode = ({ value, lang: _lang, meta: _meta }) => {
742
+ const buildCode = ({ value, lang: _lang, meta: _meta, }) => {
741
743
  // FIXME: transform to text for now
742
744
  return new docx.Paragraph({
743
745
  children: [buildText(value, {})],
@@ -768,17 +770,14 @@ const buildText = (text, deco) => {
768
770
  const buildBreak = (_) => {
769
771
  return new docx.TextRun({ text: "", break: 1 });
770
772
  };
771
- const buildLink = ({ children, url, title: _title }, ctx) => {
772
- const { nodes, footnotes: nestedFootnotes } = convertNodes(children, ctx);
773
- return {
774
- link: new docx.ExternalHyperlink({
775
- link: url,
776
- children: nodes,
777
- }),
778
- footnotes: nestedFootnotes,
779
- };
773
+ const buildLink = ({ children, url }, ctx) => {
774
+ const nodes = convertNodes(children, ctx);
775
+ return new docx.ExternalHyperlink({
776
+ link: url,
777
+ children: nodes,
778
+ });
780
779
  };
781
- const buildImage = ({ url, title: _title, alt: _alt }, images) => {
780
+ const buildImage = ({ url }, images) => {
782
781
  const img = images[url];
783
782
  invariant(img, `Fetch image was failed: ${url}`);
784
783
  const { image, width, height } = img;
@@ -790,32 +789,25 @@ const buildImage = ({ url, title: _title, alt: _alt }, images) => {
790
789
  },
791
790
  });
792
791
  };
793
- const buildFootnote = ({ children }, ctx) => {
794
- // Generate auto ID based on current registry size to ensure uniqueness
795
- const registry = ctx.footnoteRegistry || {};
796
- const autoId = `inline-${Object.keys(registry).length + 1}`;
797
- const footnoteId = getOrCreateFootnoteId(autoId, registry);
798
- const footnoteContent = children.map((node) => {
799
- // Convert each node and extract the first result as a paragraph
800
- const { nodes } = convertNodes([node], ctx);
801
- if (nodes[0] instanceof docx.Paragraph) {
802
- return nodes[0];
803
- }
804
- // For non-paragraph content, wrap in a paragraph
805
- return new docx.Paragraph({ children: nodes });
806
- });
807
- return {
808
- footnoteRef: new docx.FootnoteReferenceRun(footnoteId),
809
- footnoteData: {
810
- [footnoteId]: { children: footnoteContent },
811
- },
812
- };
792
+ const buildLinkReference = ({ children, identifier }, ctx) => {
793
+ const def = ctx.def[identifier];
794
+ if (def == null) {
795
+ return convertNodes(children, ctx);
796
+ }
797
+ return [buildLink({ children, url: def }, ctx)];
813
798
  };
814
- const buildFootnoteDefinition = ({ children }, ctx) => {
815
- return {
799
+ const buildImageReference = ({ identifier }, ctx) => {
800
+ const def = ctx.def[identifier];
801
+ if (def == null) {
802
+ return;
803
+ }
804
+ return buildImage({ url: def }, ctx.images);
805
+ };
806
+ const registerFootnoteDefinition = ({ children, identifier }, ctx) => {
807
+ const definition = {
816
808
  children: children.map((node) => {
817
809
  // Convert each node and extract the first result as a paragraph
818
- const { nodes } = convertNodes([node], ctx);
810
+ const nodes = convertNodes([node], ctx);
819
811
  if (nodes[0] instanceof docx.Paragraph) {
820
812
  return nodes[0];
821
813
  }
@@ -823,10 +815,10 @@ const buildFootnoteDefinition = ({ children }, ctx) => {
823
815
  return new docx.Paragraph({ children: nodes });
824
816
  }),
825
817
  };
818
+ ctx.footnote.def(identifier, definition);
826
819
  };
827
820
  const buildFootnoteReference = ({ identifier }, ctx) => {
828
- const footnoteId = getOrCreateFootnoteId(identifier, ctx.footnoteRegistry || {});
829
- return new docx.FootnoteReferenceRun(footnoteId);
821
+ return new docx.FootnoteReferenceRun(ctx.footnote.ref(identifier));
830
822
  };
831
823
 
832
824
  const plugin = function (opts = {}) {
@@ -839,14 +831,34 @@ const plugin = function (opts = {}) {
839
831
  unistUtilVisit.visit(node, "image", (node) => {
840
832
  imageList.push(node);
841
833
  });
834
+ const defs = new Map();
835
+ unistUtilVisit.visit(node, "definition", (node) => {
836
+ defs.set(node.identifier, node);
837
+ });
838
+ unistUtilVisit.visit(node, "imageReference", (node) => {
839
+ const maybeImage = defs.get(node.identifier);
840
+ if (maybeImage) {
841
+ imageList.push(maybeImage);
842
+ }
843
+ });
842
844
  if (imageList.length === 0) {
843
845
  return node;
844
846
  }
845
847
  const imageResolver = opts.imageResolver;
846
848
  invariant(imageResolver, "options.imageResolver is not defined.");
847
- const imageDatas = await Promise.all(imageList.map(({ url }) => imageResolver(url)));
848
- images = imageList.reduce((acc, img, i) => {
849
- acc[img.url] = imageDatas[i];
849
+ const resolved = new Set();
850
+ const promises = [];
851
+ imageList.forEach(({ url }) => {
852
+ if (!resolved.has(url)) {
853
+ resolved.add(url);
854
+ promises.push((async () => {
855
+ const img = await imageResolver(url);
856
+ return { img, url };
857
+ })());
858
+ }
859
+ });
860
+ images = (await Promise.all(promises)).reduce((acc, { img, url }) => {
861
+ acc[url] = img;
850
862
  return acc;
851
863
  }, {});
852
864
  return node;