From 06e2b7109fd510e8289e93b482bf01a1a3b8efb2 Mon Sep 17 00:00:00 2001 From: hgodinho Date: Sat, 13 May 2023 12:23:33 -0300 Subject: [PATCH 1/4] refactor:prepare for footnotes --- .editorconfig | 5 +++ src/transformer.ts | 77 +++++++++++++++++++++++++++++----------------- 2 files changed, 54 insertions(+), 28 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1d86655 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +root = true + +[*] +tab_width = 2 +indent_style = space \ No newline at end of file diff --git a/src/transformer.ts b/src/transformer.ts index 3542523..929217d 100644 --- a/src/transformer.ts +++ b/src/transformer.ts @@ -143,6 +143,16 @@ export interface DocxOptions type DocxChild = Paragraph | Table | TableOfContents; type DocxContent = DocxChild | ParagraphChild; +export interface Footnotes { + [key: number]: DocxContent; +} + +// type to define the return value of `convertNodes` +export interface ConvertNodesReturn { + nodes: DocxContent[]; + footnotes: Footnotes; +} + export const mdastToDocx = ( node: mdast.Root, { @@ -159,11 +169,11 @@ export const mdastToDocx = ( }: DocxOptions, images: ImageDataMap ): Promise => { - const nodes = convertNodes(node.children, { + const { nodes, footnotes } = convertNodes(node.children, { deco: {}, images, indent: 0, - }) as DocxChild[]; + }); const doc = new Document({ title, subject, @@ -174,7 +184,7 @@ export const mdastToDocx = ( revision, styles, background, - sections: [{ children: nodes }], + sections: [{ children: nodes as DocxChild[] }], numbering: { config: [ { @@ -193,9 +203,9 @@ export const mdastToDocx = ( } }; -const convertNodes = (nodes: mdast.Content[], ctx: Context): DocxContent[] => { +const convertNodes = (nodes: mdast.Content[], ctx: Context): ConvertNodesReturn => { const results: DocxContent[] = []; - + const footnotes: Footnotes = {}; for (const node of nodes) { switch (node.type) { case "paragraph": @@ -247,11 +257,12 @@ const convertNodes = (nodes: mdast.Content[], ctx: Context): DocxContent[] => { case "strong": case "delete": { const { type, children } = node; + const { nodes } = convertNodes(children, { + ...ctx, + deco: { ...ctx.deco, [type]: true }, + }); results.push( - ...convertNodes(children, { - ...ctx, - deco: { ...ctx.deco, [type]: true }, - }) + ...nodes ); break; } @@ -291,32 +302,36 @@ const convertNodes = (nodes: mdast.Content[], ctx: Context): DocxContent[] => { break; } } - return results; + return { + nodes: results, + footnotes, + }; }; const buildParagraph = ({ children }: mdast.Paragraph, ctx: Context) => { const list = ctx.list; + const { nodes } = convertNodes(children, ctx); return new Paragraph({ - children: convertNodes(children, ctx), + children: nodes, indent: ctx.indent > 0 ? { - start: convertInchesToTwip(INDENT * ctx.indent), - } + start: convertInchesToTwip(INDENT * ctx.indent), + } : undefined, ...(list && (list.ordered ? { - numbering: { - reference: ORDERED_LIST_REF, - level: list.level, - }, - } + numbering: { + reference: ORDERED_LIST_REF, + level: list.level, + }, + } : { - bullet: { - level: list.level, - }, - })), + bullet: { + level: list.level, + }, + })), }); }; @@ -342,9 +357,10 @@ const buildHeading = ({ children, depth }: mdast.Heading, ctx: Context) => { heading = HeadingLevel.HEADING_5; break; } + const { nodes } = convertNodes(children, ctx); return new Paragraph({ heading, - children: convertNodes(children, ctx), + children: nodes, }); }; @@ -355,7 +371,8 @@ const buildThematicBreak = (_: mdast.ThematicBreak) => { }; const buildBlockquote = ({ children }: mdast.Blockquote, ctx: Context) => { - return convertNodes(children, { ...ctx, indent: ctx.indent + 1 }); + const { nodes } = convertNodes(children, { ...ctx, indent: ctx.indent + 1 }); + return nodes; }; const buildList = ( @@ -381,7 +398,8 @@ const buildListItem = ( { children, checked: _checked, spread: _spread }: mdast.ListItem, ctx: Context ) => { - return convertNodes(children, ctx); + const { nodes } = convertNodes(children, ctx); + return nodes; }; const buildTable = ({ children, align }: mdast.Table, ctx: Context) => { @@ -422,11 +440,12 @@ const buildTableCell = ( ctx: Context, align: AlignmentType | undefined ) => { + const { nodes } = convertNodes(children, ctx); return new TableCell({ children: [ new Paragraph({ alignment: align, - children: convertNodes(children, ctx), + children: nodes, }), ], }); @@ -482,9 +501,10 @@ const buildLink = ( { children, url, title: _title }: mdast.Link, ctx: Context ) => { + const { nodes } = convertNodes(children, ctx); return new ExternalHyperlink({ link: url, - children: convertNodes(children, ctx), + children: nodes, }); }; @@ -507,7 +527,8 @@ const buildImage = ( const buildFootnote = ({ children }: mdast.Footnote, ctx: Context) => { // FIXME: transform to paragraph for now + const { nodes } = convertNodes(children, ctx); return new Paragraph({ - children: convertNodes(children, ctx), + children: nodes, }); }; From 1a3e2ca49c2be913d53a058632eafc5e0ff188a0 Mon Sep 17 00:00:00 2001 From: hgodinho Date: Sat, 13 May 2023 14:22:49 -0300 Subject: [PATCH 2/4] =footnote support --- src/transformer.ts | 33 +++++++++++++++++++++++++-------- stories/playground.stories.tsx | 2 +- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/transformer.ts b/src/transformer.ts index 929217d..0a15e35 100644 --- a/src/transformer.ts +++ b/src/transformer.ts @@ -17,6 +17,7 @@ import { AlignmentType, IImageOptions, ILevelsOptions, + FootnoteReferenceRun, } from "docx"; import type { IPropertiesOptions } from "docx/build/file/core-properties"; import type * as mdast from "./models/mdast"; @@ -144,7 +145,7 @@ type DocxChild = Paragraph | Table | TableOfContents; type DocxContent = DocxChild | ParagraphChild; export interface Footnotes { - [key: number]: DocxContent; + [key: string]: { children: Paragraph[] }; } // type to define the return value of `convertNodes` @@ -184,6 +185,7 @@ export const mdastToDocx = ( revision, styles, background, + footnotes, sections: [{ children: nodes as DocxChild[] }], numbering: { config: [ @@ -203,9 +205,12 @@ export const mdastToDocx = ( } }; -const convertNodes = (nodes: mdast.Content[], ctx: Context): ConvertNodesReturn => { +const convertNodes = ( + nodes: mdast.Content[], + ctx: Context +): ConvertNodesReturn => { const results: DocxContent[] = []; - const footnotes: Footnotes = {}; + let footnotes: Footnotes = {}; for (const node of nodes) { switch (node.type) { case "paragraph": @@ -248,7 +253,7 @@ const convertNodes = (nodes: mdast.Content[], ctx: Context): ConvertNodesReturn // FIXME: unimplemented break; case "footnoteDefinition": - // FIXME: unimplemented + footnotes[node.identifier] = buildFootnoteDefinition(node, ctx); break; case "text": results.push(buildText(node.value, ctx.deco)); @@ -261,9 +266,7 @@ const convertNodes = (nodes: mdast.Content[], ctx: Context): ConvertNodesReturn ...ctx, deco: { ...ctx.deco, [type]: true }, }); - results.push( - ...nodes - ); + results.push(...nodes); break; } case "inlineCode": @@ -289,7 +292,7 @@ const convertNodes = (nodes: mdast.Content[], ctx: Context): ConvertNodesReturn results.push(buildFootnote(node, ctx)); break; case "footnoteReference": - // FIXME: unimplemented + results.push(buildFootnoteReference(node, ctx)); break; case "math": results.push(...buildMath(node)); @@ -532,3 +535,17 @@ const buildFootnote = ({ children }: mdast.Footnote, ctx: Context) => { children: nodes, }); }; + +const buildFootnoteDefinition = ({ children }: mdast.FootnoteDefinition, ctx: Context) => { + return { + children: children.map((node) => { + const { nodes } = convertNodes([node], ctx); + return nodes[0] as Paragraph; + }), + }; +}; + +const buildFootnoteReference = ({ identifier }: mdast.FootnoteReference, ctx: Context) => { + // do we need Context? + return new FootnoteReferenceRun(parseInt(identifier)); +}; \ No newline at end of file diff --git a/stories/playground.stories.tsx b/stories/playground.stories.tsx index fc3e9aa..26ed3c7 100644 --- a/stories/playground.stories.tsx +++ b/stories/playground.stories.tsx @@ -7,7 +7,7 @@ import math from "remark-math"; import docx from "../src"; import TextEditor from "./components/text-editor"; // @ts-expect-error no type definition -import text from "../fixtures/article.md"; +import text from "../fixtures/node-footnotes.md"; import { saveAs } from "file-saver"; import { renderAsync } from "docx-preview"; From ec3219b1f5854d321711e338963215d52e3286fc Mon Sep 17 00:00:00 2001 From: hgodinho Date: Sat, 13 May 2023 23:46:55 -0300 Subject: [PATCH 3/4] remove ctx from buildFootnoteReference git check --- src/transformer.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/transformer.ts b/src/transformer.ts index 0a15e35..81d109d 100644 --- a/src/transformer.ts +++ b/src/transformer.ts @@ -120,16 +120,16 @@ type Context = Readonly<{ export interface DocxOptions extends Pick< - IPropertiesOptions, - | "title" - | "subject" - | "creator" - | "keywords" - | "description" - | "lastModifiedBy" - | "revision" - | "styles" - | "background" + IPropertiesOptions, + | "title" + | "subject" + | "creator" + | "keywords" + | "description" + | "lastModifiedBy" + | "revision" + | "styles" + | "background" > { /** * Set output type of `VFile.result`. `buffer` is `Promise`. `blob` is `Promise`. @@ -292,7 +292,8 @@ const convertNodes = ( results.push(buildFootnote(node, ctx)); break; case "footnoteReference": - results.push(buildFootnoteReference(node, ctx)); + // do we need context here? + results.push(buildFootnoteReference(node)); break; case "math": results.push(...buildMath(node)); @@ -545,7 +546,7 @@ const buildFootnoteDefinition = ({ children }: mdast.FootnoteDefinition, ctx: Co }; }; -const buildFootnoteReference = ({ identifier }: mdast.FootnoteReference, ctx: Context) => { +const buildFootnoteReference = ({ identifier }: mdast.FootnoteReference) => { // do we need Context? return new FootnoteReferenceRun(parseInt(identifier)); }; \ No newline at end of file From aab3400ecd8da14c64b292d91dd85f0336951ac1 Mon Sep 17 00:00:00 2001 From: hgodinho Date: Sun, 14 May 2023 12:34:54 -0300 Subject: [PATCH 4/4] snapshot update --- src/__snapshots__/index.spec.ts.snap | 52 ++++++++++++++-------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/__snapshots__/index.spec.ts.snap b/src/__snapshots__/index.spec.ts.snap index ce16b4d..0306fc3 100644 --- a/src/__snapshots__/index.spec.ts.snap +++ b/src/__snapshots__/index.spec.ts.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`e2e article.md 1`] = ` -"heading 1heading 2heading 3heading 4heading 5heading 6aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaboldemphasisdeleteinline codelistlistlistlistlistnot checkedcheckedordered listordered listordered listaaaaaaeeeeeeeeeeaaaordered listThis is link to GitHub.com.This is .quote +"heading 1heading 2heading 3heading 4heading 5heading 6aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaboldemphasisdeleteinline codelistlistlistlistlistnot checkedcheckedordered listordered listordered listaaaaaaeeeeeeeeeeaaaordered listThis is link to GitHub.com.This is .quote quote quote quotequotequoted quoteLeft alignRight alignCenter alignThisThisThiscolumncolumncolumnwillwillwillbebebeleftrightcenteralignedalignedalignedL=12ρv2SCL<div style="color:red;"> @@ -31,7 +31,7 @@ export $initHighlight;"`; -exports[`e2e article.md 3`] = `""`; +exports[`e2e article.md 3`] = `""`; exports[`e2e article.md 4`] = `""`; @@ -39,11 +39,11 @@ exports[`e2e article.md 5`] = `""`; -exports[`e2e node-break.md 1`] = `""`; +exports[`e2e node-break.md 1`] = `""`; exports[`e2e node-break.md 2`] = `""`; -exports[`e2e node-break.md 3`] = `""`; +exports[`e2e node-break.md 3`] = `""`; exports[`e2e node-break.md 4`] = `""`; @@ -52,7 +52,7 @@ exports[`e2e node-break.md 5`] = `""`; exports[`e2e node-code.md 1`] = ` -"@font-face { +"@font-face { font-family: Chunkfive; src: url("Chunkfive.otf"); } @@ -108,7 +108,7 @@ myArray.forEach(() => { }); // fat arrow syntax"`; -exports[`e2e node-code.md 3`] = `""`; +exports[`e2e node-code.md 3`] = `""`; exports[`e2e node-code.md 4`] = `""`; @@ -116,23 +116,23 @@ exports[`e2e node-code.md 5`] = `""`; -exports[`e2e node-footnotes.md 1`] = `"This is footnote. And this is also inline footnote."`; +exports[`e2e node-footnotes.md 1`] = `"This is footnote. And this is also inline footnote."`; exports[`e2e node-footnotes.md 2`] = `""`; -exports[`e2e node-footnotes.md 3`] = `""`; +exports[`e2e node-footnotes.md 3`] = `""`; -exports[`e2e node-footnotes.md 4`] = `""`; +exports[`e2e node-footnotes.md 4`] = `"Here is the footnote."`; exports[`e2e node-footnotes.md 5`] = `""`; exports[`e2e node-footnotes.md 6`] = `""`; -exports[`e2e node-frontmatter-toml.md 1`] = `"Other markdownblablabla"`; +exports[`e2e node-frontmatter-toml.md 1`] = `"Other markdownblablabla"`; exports[`e2e node-frontmatter-toml.md 2`] = `""`; -exports[`e2e node-frontmatter-toml.md 3`] = `""`; +exports[`e2e node-frontmatter-toml.md 3`] = `""`; exports[`e2e node-frontmatter-toml.md 4`] = `""`; @@ -140,11 +140,11 @@ exports[`e2e node-frontmatter-toml.md 5`] = `""`; -exports[`e2e node-frontmatter-yaml.md 1`] = `"Other markdownblablabla"`; +exports[`e2e node-frontmatter-yaml.md 1`] = `"Other markdownblablabla"`; exports[`e2e node-frontmatter-yaml.md 2`] = `""`; -exports[`e2e node-frontmatter-yaml.md 3`] = `""`; +exports[`e2e node-frontmatter-yaml.md 3`] = `""`; exports[`e2e node-frontmatter-yaml.md 4`] = `""`; @@ -152,11 +152,11 @@ exports[`e2e node-frontmatter-yaml.md 5`] = `""`; -exports[`e2e node-heading.md 1`] = `"AlphaaaaaBravobbbbCharlieccccDeltaddddEFGH"`; +exports[`e2e node-heading.md 1`] = `"AlphaaaaaBravobbbbCharlieccccDeltaddddEFGH"`; exports[`e2e node-heading.md 2`] = `""`; -exports[`e2e node-heading.md 3`] = `""`; +exports[`e2e node-heading.md 3`] = `""`; exports[`e2e node-heading.md 4`] = `""`; @@ -165,13 +165,13 @@ exports[`e2e node-heading.md 5`] = `""`; exports[`e2e node-math.md 1`] = ` -"Lift(L) can be determined by Lift Coefficient (CL) like the following +"Lift(L) can be determined by Lift Coefficient (CL) like the following equation.L=12ρv2SCL" `; exports[`e2e node-math.md 2`] = `""`; -exports[`e2e node-math.md 3`] = `""`; +exports[`e2e node-math.md 3`] = `""`; exports[`e2e node-math.md 4`] = `""`; @@ -180,14 +180,14 @@ exports[`e2e node-math.md 5`] = `""`; exports[`e2e node-ml.md 1`] = ` -"<div> +"<div> <span>This is <b>html</b></span> </div><!--comment-->mailto:foobarbazaaaaaa" `; exports[`e2e node-ml.md 2`] = `""`; -exports[`e2e node-ml.md 3`] = `""`; +exports[`e2e node-ml.md 3`] = `""`; exports[`e2e node-ml.md 4`] = `""`; @@ -196,13 +196,13 @@ exports[`e2e node-ml.md 5`] = `""`; exports[`e2e node-paragraph.md 1`] = ` -"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." `; exports[`e2e node-paragraph.md 2`] = `""`; -exports[`e2e node-paragraph.md 3`] = `""`; +exports[`e2e node-paragraph.md 3`] = `""`; exports[`e2e node-paragraph.md 4`] = `""`; @@ -211,13 +211,13 @@ exports[`e2e node-paragraph.md 5`] = `""`; exports[`e2e node-phrasing-1.md 1`] = ` -"aaa aaaaaaa aaaaaa aaaaaaaa aaaaaaaa aaaaaaa aaaa aaaaaaaa aaa -aaaaaaaaaa aa a aa aa aaa aaaaa aaaa aaa*aa*aa_aa_a~~aa~~aheadinglistquotelink to GitHub.comlink to GitHub.com" +"aaa aaaaaaa aaaaaa aaaaaaaa aaaaaaaa aaaaaaa aaaa aaaaaaaa aaa +aaaaaaaaaa aa a aa aa aaa aaaaa aaaa aaa*aa*aa_aa_a~~aa~~aheadinglistquotelink to GitHub.comlink to GitHub.com" `; exports[`e2e node-phrasing-1.md 2`] = `""`; -exports[`e2e node-phrasing-1.md 3`] = `""`; +exports[`e2e node-phrasing-1.md 3`] = `""`; exports[`e2e node-phrasing-1.md 4`] = `""`; @@ -225,11 +225,11 @@ exports[`e2e node-phrasing-1.md 5`] = `""`; -exports[`e2e node-phrasing-2.md 1`] = `"link to GitHub.com"`; +exports[`e2e node-phrasing-2.md 1`] = `"link to GitHub.com"`; exports[`e2e node-phrasing-2.md 2`] = `""`; -exports[`e2e node-phrasing-2.md 3`] = `""`; +exports[`e2e node-phrasing-2.md 3`] = `""`; exports[`e2e node-phrasing-2.md 4`] = `""`;