remark-docx 0.1.5 → 0.1.7

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
@@ -1,6 +1,6 @@
1
1
  # remark-docx
2
2
 
3
- ![npm](https://img.shields.io/npm/v/remark-docx) ![check](https://github.com/inokawa/remark-docx/workflows/check/badge.svg) ![demo](https://github.com/inokawa/remark-docx/workflows/demo/badge.svg)
3
+ ![npm](https://img.shields.io/npm/v/remark-docx) ![npm](https://img.shields.io/npm/dw/remark-docx) ![check](https://github.com/inokawa/remark-docx/workflows/check/badge.svg) ![demo](https://github.com/inokawa/remark-docx/workflows/demo/badge.svg)
4
4
 
5
5
  [remark](https://github.com/remarkjs/remark) plugin to compile markdown to docx (Microsoft Word, Office Open XML).
6
6
 
@@ -100,10 +100,11 @@ If you find a problem, feel free to create an [issue](https://github.com/inokawa
100
100
 
101
101
  ### Making a Pull Request
102
102
 
103
- 1. Clone this repo.
103
+ 1. Fork this repo.
104
104
  2. Run `npm install`.
105
105
  3. Commit your fix.
106
- 4. Make a PR and confirm all the CI checks passed.
106
+ 4. Add tests to cover the fix.
107
+ 5. Make a PR and confirm all the CI checks passed.
107
108
 
108
109
  ## Related projects
109
110
 
package/lib/index.js CHANGED
@@ -1,13 +1,18 @@
1
1
  'use strict';
2
2
 
3
- var tslib = require('tslib');
4
3
  var unistUtilVisit = require('unist-util-visit');
5
4
  var docx = require('docx');
6
5
  var unifiedLatexUtilParse = require('@unified-latex/unified-latex-util-parse');
7
6
 
7
+ /**
8
+ * @internal
9
+ */
8
10
  const unreachable = (_) => {
9
11
  throw new Error("unreachable");
10
12
  };
13
+ /**
14
+ * @internal
15
+ */
11
16
  function invariant(cond, message) {
12
17
  if (!cond)
13
18
  throw new Error(message);
@@ -337,6 +342,9 @@ const mapNode = (n, runs) => {
337
342
  }
338
343
  return [];
339
344
  };
345
+ /**
346
+ * @internal
347
+ */
340
348
  const parseLatex = (value) => {
341
349
  const parsed = unifiedLatexUtilParse.parseMath(value);
342
350
  const paragraphs = [[]];
@@ -419,11 +427,13 @@ const DEFAULT_NUMBERINGS = [
419
427
  },
420
428
  },
421
429
  ];
422
- const mdastToDocx = (node, { output = "buffer", title, subject, creator, keywords, description, lastModifiedBy, revision, styles, background, }, images) => tslib.__awaiter(void 0, void 0, void 0, function* () {
430
+ const mdastToDocx = async (node, { output = "buffer", title, subject, creator, keywords, description, lastModifiedBy, revision, styles, background, }, images) => {
431
+ const footnoteRegistry = {};
423
432
  const { nodes, footnotes } = convertNodes(node.children, {
424
433
  deco: {},
425
434
  images,
426
435
  indent: 0,
436
+ footnoteRegistry,
427
437
  });
428
438
  const doc = new docx.Document({
429
439
  title,
@@ -448,7 +458,7 @@ const mdastToDocx = (node, { output = "buffer", title, subject, creator, keyword
448
458
  });
449
459
  switch (output) {
450
460
  case "buffer":
451
- const bufOut = yield docx.Packer.toBuffer(doc);
461
+ const bufOut = await docx.Packer.toBuffer(doc);
452
462
  // feature detection instead of environment detection, but if Buffer exists
453
463
  // it's probably Node. If not, return the Uint8Array that JSZip returns
454
464
  // when it doesn't detect a Node environment.
@@ -456,27 +466,45 @@ const mdastToDocx = (node, { output = "buffer", title, subject, creator, keyword
456
466
  case "blob":
457
467
  return docx.Packer.toBlob(doc);
458
468
  }
459
- });
469
+ };
470
+ const getOrCreateFootnoteId = (identifier, registry) => {
471
+ if (!(identifier in registry)) {
472
+ registry[identifier] = Object.keys(registry).length + 1;
473
+ }
474
+ return registry[identifier];
475
+ };
460
476
  const convertNodes = (nodes, ctx) => {
461
477
  const results = [];
462
478
  let footnotes = {};
463
479
  for (const node of nodes) {
464
480
  switch (node.type) {
465
- case "paragraph":
466
- results.push(buildParagraph(node, ctx));
481
+ case "paragraph": {
482
+ const { paragraph, footnotes: nestedFootnotes } = buildParagraph(node, ctx);
483
+ results.push(paragraph);
484
+ footnotes = { ...footnotes, ...nestedFootnotes };
467
485
  break;
468
- case "heading":
469
- results.push(buildHeading(node, ctx));
486
+ }
487
+ case "heading": {
488
+ const { heading, footnotes: nestedFootnotes } = buildHeading(node, ctx);
489
+ results.push(heading);
490
+ footnotes = { ...footnotes, ...nestedFootnotes };
470
491
  break;
492
+ }
471
493
  case "thematicBreak":
472
494
  results.push(buildThematicBreak());
473
495
  break;
474
- case "blockquote":
475
- results.push(...buildBlockquote(node, ctx));
496
+ case "blockquote": {
497
+ const { nodes: blockquoteNodes, footnotes: nestedFootnotes } = buildBlockquote(node, ctx);
498
+ results.push(...blockquoteNodes);
499
+ footnotes = { ...footnotes, ...nestedFootnotes };
476
500
  break;
477
- case "list":
478
- results.push(...buildList(node, ctx));
501
+ }
502
+ case "list": {
503
+ const { nodes: listNodes, footnotes: nestedFootnotes } = buildList(node, ctx);
504
+ results.push(...listNodes);
505
+ footnotes = { ...footnotes, ...nestedFootnotes };
479
506
  break;
507
+ }
480
508
  case "listItem":
481
509
  invariant(false, "unreachable");
482
510
  case "table":
@@ -501,9 +529,11 @@ const convertNodes = (nodes, ctx) => {
501
529
  case "definition":
502
530
  // FIXME: unimplemented
503
531
  break;
504
- case "footnoteDefinition":
505
- footnotes[node.identifier] = buildFootnoteDefinition(node, ctx);
532
+ case "footnoteDefinition": {
533
+ const footnoteId = getOrCreateFootnoteId(node.identifier, ctx.footnoteRegistry || {});
534
+ footnotes[footnoteId] = buildFootnoteDefinition(node, ctx);
506
535
  break;
536
+ }
507
537
  case "text":
508
538
  results.push(buildText(node.value, ctx.deco));
509
539
  break;
@@ -511,8 +541,12 @@ const convertNodes = (nodes, ctx) => {
511
541
  case "strong":
512
542
  case "delete": {
513
543
  const { type, children } = node;
514
- const { nodes } = convertNodes(children, Object.assign(Object.assign({}, ctx), { deco: Object.assign(Object.assign({}, ctx.deco), { [type]: true }) }));
544
+ const { nodes, footnotes: nestedFootnotes } = convertNodes(children, {
545
+ ...ctx,
546
+ deco: { ...ctx.deco, [type]: true },
547
+ });
515
548
  results.push(...nodes);
549
+ footnotes = { ...footnotes, ...nestedFootnotes };
516
550
  break;
517
551
  }
518
552
  case "inlineCode":
@@ -522,9 +556,12 @@ const convertNodes = (nodes, ctx) => {
522
556
  case "break":
523
557
  results.push(buildBreak());
524
558
  break;
525
- case "link":
526
- results.push(buildLink(node, ctx));
559
+ case "link": {
560
+ const { link, footnotes: nestedFootnotes } = buildLink(node, ctx);
561
+ results.push(link);
562
+ footnotes = { ...footnotes, ...nestedFootnotes };
527
563
  break;
564
+ }
528
565
  case "image":
529
566
  results.push(buildImage(node, ctx.images));
530
567
  break;
@@ -534,12 +571,14 @@ const convertNodes = (nodes, ctx) => {
534
571
  case "imageReference":
535
572
  // FIXME: unimplemented
536
573
  break;
537
- case "footnote":
538
- results.push(buildFootnote(node, ctx));
574
+ case "footnote": {
575
+ const { footnoteRef, footnoteData } = buildFootnote(node, ctx);
576
+ results.push(footnoteRef);
577
+ footnotes = { ...footnotes, ...footnoteData };
539
578
  break;
579
+ }
540
580
  case "footnoteReference":
541
- // do we need context here?
542
- results.push(buildFootnoteReference(node));
581
+ results.push(buildFootnoteReference(node, ctx));
543
582
  break;
544
583
  case "math":
545
584
  results.push(...buildMath(node));
@@ -559,60 +598,69 @@ const convertNodes = (nodes, ctx) => {
559
598
  };
560
599
  const buildParagraph = ({ children }, ctx) => {
561
600
  const list = ctx.list;
562
- const { nodes } = convertNodes(children, ctx);
563
- const paragraphChildren = [...nodes];
601
+ const { nodes, footnotes: nestedFootnotes } = convertNodes(children, ctx);
564
602
  if (list && list.checked != null) {
565
- paragraphChildren.unshift(new docx.CheckBox({
603
+ nodes.unshift(new docx.CheckBox({
566
604
  checked: list.checked,
567
605
  checkedState: { value: "2611" },
568
606
  uncheckedState: { value: "2610" },
569
607
  }));
570
608
  }
571
- return new docx.Paragraph(Object.assign({ children: paragraphChildren, indent: ctx.indent > 0
572
- ? {
573
- start: docx.convertInchesToTwip(INDENT * ctx.indent),
574
- }
575
- : undefined }, (list &&
576
- (list.ordered
577
- ? {
578
- numbering: {
579
- reference: ORDERED_LIST_REF,
580
- level: list.level,
581
- },
582
- }
583
- : {
584
- bullet: {
585
- level: list.level,
586
- },
587
- }))));
609
+ return {
610
+ paragraph: new docx.Paragraph({
611
+ children: nodes,
612
+ indent: ctx.indent > 0
613
+ ? {
614
+ start: docx.convertInchesToTwip(INDENT * ctx.indent),
615
+ }
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
+ };
588
633
  };
589
634
  const buildHeading = ({ children, depth }, ctx) => {
590
- let heading;
635
+ let headingLevel;
591
636
  switch (depth) {
592
637
  case 1:
593
- heading = docx.HeadingLevel.TITLE;
638
+ headingLevel = docx.HeadingLevel.TITLE;
594
639
  break;
595
640
  case 2:
596
- heading = docx.HeadingLevel.HEADING_1;
641
+ headingLevel = docx.HeadingLevel.HEADING_1;
597
642
  break;
598
643
  case 3:
599
- heading = docx.HeadingLevel.HEADING_2;
644
+ headingLevel = docx.HeadingLevel.HEADING_2;
600
645
  break;
601
646
  case 4:
602
- heading = docx.HeadingLevel.HEADING_3;
647
+ headingLevel = docx.HeadingLevel.HEADING_3;
603
648
  break;
604
649
  case 5:
605
- heading = docx.HeadingLevel.HEADING_4;
650
+ headingLevel = docx.HeadingLevel.HEADING_4;
606
651
  break;
607
652
  case 6:
608
- heading = docx.HeadingLevel.HEADING_5;
653
+ headingLevel = docx.HeadingLevel.HEADING_5;
609
654
  break;
610
655
  }
611
- const { nodes } = convertNodes(children, ctx);
612
- return new docx.Paragraph({
613
- heading,
614
- children: nodes,
615
- });
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
+ };
616
664
  };
617
665
  const buildThematicBreak = (_) => {
618
666
  return new docx.Paragraph({
@@ -620,22 +668,31 @@ const buildThematicBreak = (_) => {
620
668
  });
621
669
  };
622
670
  const buildBlockquote = ({ children }, ctx) => {
623
- const { nodes } = convertNodes(children, Object.assign(Object.assign({}, ctx), { indent: ctx.indent + 1 }));
624
- return nodes;
671
+ const { nodes, footnotes: nestedFootnotes } = convertNodes(children, { ...ctx, indent: ctx.indent + 1 });
672
+ return { nodes, footnotes: nestedFootnotes };
625
673
  };
626
674
  const buildList = ({ children, ordered, start: _start, spread: _spread }, ctx) => {
627
675
  const list = {
628
676
  level: ctx.list ? ctx.list.level + 1 : 0,
629
677
  ordered: !!ordered,
630
678
  };
631
- return children.reduce((acc, item) => {
632
- acc.push(...buildListItem(item, Object.assign(Object.assign({}, ctx), { list })));
633
- return acc;
634
- }, []);
679
+ let allFootnotes = {};
680
+ const allNodes = children.flatMap((item) => {
681
+ const { nodes, footnotes: itemFootnotes } = buildListItem(item, {
682
+ ...ctx,
683
+ list,
684
+ });
685
+ allFootnotes = { ...allFootnotes, ...itemFootnotes };
686
+ return nodes;
687
+ });
688
+ return { nodes: allNodes, footnotes: allFootnotes };
635
689
  };
636
690
  const buildListItem = ({ children, checked, spread: _spread }, ctx) => {
637
- const { nodes } = convertNodes(children, Object.assign(Object.assign({}, ctx), (ctx.list && { list: Object.assign(Object.assign({}, ctx.list), { checked: checked !== null && checked !== void 0 ? checked : undefined }) })));
638
- return nodes;
691
+ const { nodes, footnotes: nestedFootnotes } = convertNodes(children, {
692
+ ...ctx,
693
+ ...(ctx.list && { list: { ...ctx.list, checked: checked !== null && checked !== void 0 ? checked : undefined } }),
694
+ });
695
+ return { nodes, footnotes: nestedFootnotes };
639
696
  };
640
697
  const buildTable = ({ children, align }, ctx) => {
641
698
  const cellAligns = align === null || align === void 0 ? void 0 : align.map((a) => {
@@ -712,11 +769,14 @@ const buildBreak = (_) => {
712
769
  return new docx.TextRun({ text: "", break: 1 });
713
770
  };
714
771
  const buildLink = ({ children, url, title: _title }, ctx) => {
715
- const { nodes } = convertNodes(children, ctx);
716
- return new docx.ExternalHyperlink({
717
- link: url,
718
- children: nodes,
719
- });
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
+ };
720
780
  };
721
781
  const buildImage = ({ url, title: _title, alt: _alt }, images) => {
722
782
  const img = images[url];
@@ -731,23 +791,42 @@ const buildImage = ({ url, title: _title, alt: _alt }, images) => {
731
791
  });
732
792
  };
733
793
  const buildFootnote = ({ children }, ctx) => {
734
- // FIXME: transform to paragraph for now
735
- const { nodes } = convertNodes(children, ctx);
736
- return new docx.Paragraph({
737
- children: nodes,
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 });
738
806
  });
807
+ return {
808
+ footnoteRef: new docx.FootnoteReferenceRun(footnoteId),
809
+ footnoteData: {
810
+ [footnoteId]: { children: footnoteContent },
811
+ },
812
+ };
739
813
  };
740
814
  const buildFootnoteDefinition = ({ children }, ctx) => {
741
815
  return {
742
816
  children: children.map((node) => {
817
+ // Convert each node and extract the first result as a paragraph
743
818
  const { nodes } = convertNodes([node], ctx);
744
- return nodes[0];
819
+ if (nodes[0] instanceof docx.Paragraph) {
820
+ return nodes[0];
821
+ }
822
+ // For non-paragraph content, wrap in a paragraph
823
+ return new docx.Paragraph({ children: nodes });
745
824
  }),
746
825
  };
747
826
  };
748
- const buildFootnoteReference = ({ identifier }) => {
749
- // do we need Context?
750
- return new docx.FootnoteReferenceRun(parseInt(identifier));
827
+ const buildFootnoteReference = ({ identifier }, ctx) => {
828
+ const footnoteId = getOrCreateFootnoteId(identifier, ctx.footnoteRegistry || {});
829
+ return new docx.FootnoteReferenceRun(footnoteId);
751
830
  };
752
831
 
753
832
  const plugin = function (opts = {}) {
@@ -755,7 +834,7 @@ const plugin = function (opts = {}) {
755
834
  this.Compiler = (node) => {
756
835
  return mdastToDocx(node, opts, images);
757
836
  };
758
- return (node) => tslib.__awaiter(this, void 0, void 0, function* () {
837
+ return async (node) => {
759
838
  const imageList = [];
760
839
  unistUtilVisit.visit(node, "image", (node) => {
761
840
  imageList.push(node);
@@ -765,13 +844,13 @@ const plugin = function (opts = {}) {
765
844
  }
766
845
  const imageResolver = opts.imageResolver;
767
846
  invariant(imageResolver, "options.imageResolver is not defined.");
768
- const imageDatas = yield Promise.all(imageList.map(({ url }) => imageResolver(url)));
847
+ const imageDatas = await Promise.all(imageList.map(({ url }) => imageResolver(url)));
769
848
  images = imageList.reduce((acc, img, i) => {
770
849
  acc[img.url] = imageDatas[i];
771
850
  return acc;
772
851
  }, {});
773
852
  return node;
774
- });
853
+ };
775
854
  };
776
855
 
777
856
  module.exports = plugin;