From c969953afd30f1b4a1df9e3061e6d17104c59056 Mon Sep 17 00:00:00 2001 From: lordelogos Date: Thu, 24 Aug 2023 11:43:11 +0100 Subject: [PATCH 1/8] wip: poc works --- package.json | 5 +-- pnpm-lock.yaml | 9 +++++ src/marked.ts | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 src/marked.ts diff --git a/package.json b/package.json index 34e5bf1..aaedfc8 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ }, "license": "MIT", "devDependencies": { + "@changesets/cli": "^2.26.2", "@react-email/render": "0.0.7", "@types/jest": "29.5.2", "@types/react": "18.2.8", @@ -43,7 +44,6 @@ "ts-jest": "29.1.0", "ts-node": "10.9.1", "tsup": "6.7.0", - "@changesets/cli": "^2.26.2", "typescript": "5.1.3" }, "peerDependencies": { @@ -51,7 +51,8 @@ "react-email": ">1.9.3" }, "dependencies": { - "isomorphic-dompurify": "1.7.0" + "isomorphic-dompurify": "1.7.0", + "marked": "^7.0.4" }, "publishConfig": { "access": "public" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5dbacd1..dfdb15f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,9 @@ dependencies: isomorphic-dompurify: specifier: 1.7.0 version: 1.7.0 + marked: + specifier: ^7.0.4 + version: 7.0.4 devDependencies: '@changesets/cli': @@ -3992,6 +3995,12 @@ packages: engines: {node: '>=8'} dev: true + /marked@7.0.4: + resolution: {integrity: sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ==} + engines: {node: '>= 16'} + hasBin: true + dev: false + /meow@6.1.1: resolution: {integrity: sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==} engines: {node: '>=8'} diff --git a/src/marked.ts b/src/marked.ts new file mode 100644 index 0000000..1da3ceb --- /dev/null +++ b/src/marked.ts @@ -0,0 +1,97 @@ +import { marked, RendererObject } from "marked"; +import { StylesType } from "./types"; +// import { styles } from "./styles"; +import { CSSProperties } from "react"; +// import { parseCssInJsToInlineCss } from "./utils"; + +function camelToKebabCase(str: string): string { + return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(); +} + +function parseCssInJsToInlineCss( + cssProperties: CSSProperties | undefined +): string { + if (!cssProperties) return ""; + + return Object.entries(cssProperties) + .map(([property, value]) => `${camelToKebabCase(property)}:${value}`) + .join(";"); +} + +interface initRendererProps { + customStyles?: StylesType; + withDataAttr?: boolean; +} + +const initRenderer = ({ + customStyles, + withDataAttr = false, +}: initRendererProps): RendererObject => { + const finalStyles = { ...{}, ...customStyles }; + + const customRenderer: RendererObject = { + heading(text, level) { + return `${text}`; + }, + }; + + return customRenderer; +}; + +class MarkdownParser { + private readonly renderer: RendererObject; + + constructor({ + customStyles, + withDataAttr, + }: { + customStyles?: StylesType; + withDataAttr?: boolean; + }) { + this.renderer = initRenderer({ customStyles, withDataAttr }); + } + + parse(markdown: string) { + marked.use({ renderer: this.renderer }); + return marked.parse(markdown); + } +} + +const x = new MarkdownParser({ + customStyles: { h1: { fontWeight: "bold" } }, + withDataAttr: true, +}); +console.log(x.parse("# Hello World!")); + +/** + code(string code, string infostring, boolean escaped) +blockquote(string quote) +html(string html, boolean block) +hr() +list(string body, boolean ordered, number start) +listitem(string text, boolean task, boolean checked) +checkbox(boolean checked) +paragraph(string text) +table(string header, string body) +tablerow(string content) +tablecell(string content, object flags) +Inline-level renderer methods +strong(string text) +em(string text) +codespan(string code) +br() +del(string text) +link(string href, string title, string text) +image(string href, string title, string text) +text(string text) + */ From 7c751128e68c3dcd28eb49a9b7d78e3dfad3bcd9 Mon Sep 17 00:00:00 2001 From: lordelogos Date: Thu, 24 Aug 2023 14:17:39 +0100 Subject: [PATCH 2/8] feat: refactor complete --- __tests__/camelToKebabCase.test.ts | 2 +- __tests__/parseCssInJsToInlineCss.test.ts | 2 +- __tests__/parseMarkdownToReactEmail.test.ts | 132 --- .../parseMarkdownToReactEmailJSX.test.ts | 166 +++- __tests__/reactEmailMarkdown.test.tsx | 4 +- src/.DS_Store | Bin 6148 -> 6148 bytes src/components/reactEmailMarkdown.tsx | 2 +- src/hooks.ts | 17 + src/index.ts | 20 +- src/marked.ts | 97 --- src/parseMarkdownToReactEmailJSX.ts | 17 + src/parser.ts | 27 + src/patterns.ts | 25 - src/types.ts | 24 +- src/utils.ts | 811 +++++------------- 15 files changed, 416 insertions(+), 930 deletions(-) delete mode 100644 __tests__/parseMarkdownToReactEmail.test.ts create mode 100644 src/hooks.ts delete mode 100644 src/marked.ts create mode 100644 src/parseMarkdownToReactEmailJSX.ts create mode 100644 src/parser.ts delete mode 100644 src/patterns.ts diff --git a/__tests__/camelToKebabCase.test.ts b/__tests__/camelToKebabCase.test.ts index c1d0f02..83e46e6 100644 --- a/__tests__/camelToKebabCase.test.ts +++ b/__tests__/camelToKebabCase.test.ts @@ -1,4 +1,4 @@ -import { camelToKebabCase } from "../src"; +import { camelToKebabCase } from "../src/utils"; describe("camelToKebabCase", () => { it("should convert camel case to kebab case", () => { diff --git a/__tests__/parseCssInJsToInlineCss.test.ts b/__tests__/parseCssInJsToInlineCss.test.ts index 08e3cf0..ce8447e 100644 --- a/__tests__/parseCssInJsToInlineCss.test.ts +++ b/__tests__/parseCssInJsToInlineCss.test.ts @@ -1,4 +1,4 @@ -import { parseCssInJsToInlineCss } from "../src"; +import { parseCssInJsToInlineCss } from "../src/utils"; describe("parseCssInJsToInlineCss", () => { test("should return an empty string for undefined CSS properties", () => { diff --git a/__tests__/parseMarkdownToReactEmail.test.ts b/__tests__/parseMarkdownToReactEmail.test.ts deleted file mode 100644 index 6d50594..0000000 --- a/__tests__/parseMarkdownToReactEmail.test.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { parseMarkdownToReactEmail } from "../src"; - -describe("Markdown to React Mail Parser", () => { - it("converts headers correctly", () => { - const markdown = "# Hello, World!"; - const expected = `
Hello, World!
`; - - const rendered = parseMarkdownToReactEmail(markdown); - expect(rendered).toBe(expected); - }); - - it("converts paragraphs, headers, lists, and tables correctly", () => { - const markdown = `# Header 1 - -This is a paragraph with some text. You can have multiple paragraphs separated by empty lines. - -## Header 2 - -Here is an unordered list: -- Item 1 -- Item 2 -- Item 3 - -### Header 3 - -Here is an ordered list: -1. First -2. Second -3. Third - -#### Header 4 - -A table example: - -| Column 1 | Column 2 | -|----------|----------| -| Cell 1 | Cell 2 | -| Cell 3 | Cell 4 | -`; - - const expected = `
Header 1 - -This is a paragraph with some text. You can have multiple paragraphs separated by empty lines. - -Header 2 - -Here is an unordered list: -
  • Item 1
  • -
  • Item 2
  • -
  • Item 3
- -Header 3 - -Here is an ordered list: -
  1. First
  2. -
  3. Second
  4. -
  5. Third
  6. -
-Header 4 - -A table example: -
Column 1Column 2
Cell 1Cell 2
Cell 3Cell 4
`; - - const rendered = parseMarkdownToReactEmail(markdown); - expect(rendered).toBe(expected); - }); - - it("converts images, codeblocks, blockquotes and nested blockquotes correctly", () => { - const markdown = `##### Header 5 - -An image example: -![Alt Text](https://example.com/image.jpg) - -###### Header 6 - -Some **bold text** and *italic text* _italic text_. - -Code block example: -\`\`\` -javascript -function greet() { - console.log('Hello!'); -} -\`\`\` - -Here is a block quote example: - -> This is a block quote. -> It can span multiple lines. -> > This is a nested quote -> > With multiple -> > Lines of code - -> Block quotes are often used to highlight important information or provide references.`; - - const expected = `
Header 5 - -An image example: -\"Alt - -Header 6 - -Some bold text and italic text italic text. - -Code block example: -
-  javascript
-  function greet() {
-    console.log('Hello!');
-  }
-
- -Here is a block quote example: - - -This is a block quote. -It can span multiple lines. - -This is a nested quote -With multiple -Lines of code - - - - -Block quotes are often used to highlight important information or provide references. -
`; - - const rendered = parseMarkdownToReactEmail(markdown); - expect(rendered).toBe(expected); - }); -}); diff --git a/__tests__/parseMarkdownToReactEmailJSX.test.ts b/__tests__/parseMarkdownToReactEmailJSX.test.ts index 4f9a6c2..ae443e7 100644 --- a/__tests__/parseMarkdownToReactEmailJSX.test.ts +++ b/__tests__/parseMarkdownToReactEmailJSX.test.ts @@ -1,6 +1,88 @@ import { parseMarkdownToReactEmailJSX } from "../src"; describe("Markdown to React Mail JSX Parser", () => { + it("handles markdown correctly", () => { + const markdown = `# Markdown Test Document + +This is a **test document** to check the capabilities of a Markdown parser. + +## Headings + +### Third-Level Heading + +#### Fourth-Level Heading + +##### Fifth-Level Heading + +###### Sixth-Level Heading + +## Text Formatting + +This is some **bold text** and this is some *italic text*. You can also use ~~strikethrough~~ and \`inline code\`. + +## Lists + +1. Ordered List Item 1 +2. Ordered List Item 2 +3. Ordered List Item 3 + +- Unordered List Item 1 +- Unordered List Item 2 +- Unordered List Item 3 + +## Links + +[Markdown Guide](https://www.markdownguide.org) + +## Images + +![Markdown Logo](https://markdown-here.com/img/icon256.png) + +## Blockquotes + +> This is a blockquote. +> - Author + +## Code Blocks + +\`\`\`javascript +function greet(name) { +console.log(\`Hello, \$\{name\}!\`); +} +\`\`\``; + + const expected = `

Markdown Test Document

This is a test document to check the capabilities of a Markdown parser.

+

Headings

Third-Level Heading

Fourth-Level Heading

Fifth-Level Heading
Sixth-Level Heading

Text Formatting

This is some bold text and this is some italic text. You can also use strikethrough and inline code.

+

Lists

    +
  1. Ordered List Item 1
  2. +
  3. Ordered List Item 2
  4. +
  5. Ordered List Item 3
  6. +
+
    +
  • Unordered List Item 1
  • +
  • Unordered List Item 2
  • +
  • Unordered List Item 3
  • +
+

Links

Markdown Guide

+

Images

\"Markdown

+

Blockquotes

+

This is a blockquote.

+
    +
  • Author
  • +
+
+

Code Blocks

function greet(name) {
+console.log(\`Hello, $\{name\}!\`);
+}
+
+`; + + const rendered = parseMarkdownToReactEmailJSX({ + markdown, + }); + expect(rendered).toBe(expected); + }); + it("handles empty string correctly", () => { const markdown = ""; const expected = ``; @@ -19,13 +101,7 @@ describe("Markdown to React Mail JSX Parser", () => { ##### Header 5 ###### Header 6 `; - const expected = `

Header 1

-

Header 2

-

Header 3

-

Header 4

-
Header 5
-
Header 6
-`; + const expected = `

Header 1

Header 2

Header 3

Header 4

Header 5
Header 6
`; const rendered = parseMarkdownToReactEmailJSX({ markdown, @@ -39,9 +115,7 @@ This is one This is two `; - const expected = `

The paragraphs

-

This is one

- + const expected = `

The paragraphs

This is one

This is two

`; @@ -53,13 +127,12 @@ This is two it("handles bold, italic and strikethrough texts correctly", () => { const markdown = `# The text formats -This is **one** bold and _italic_ text +This is **one** bold and *italic* text This is ~~striked~~ text and \`inline code\``; - const expected = `

The text formats

-

This is one bold and italic text

- -

This is striked text and inline code

`; + const expected = `

The text formats

This is one bold and italic text

+

This is striked text and inline code

+`; const rendered = parseMarkdownToReactEmailJSX({ markdown, @@ -79,17 +152,19 @@ Here is an ordered list: 2. Second 3. Third `; - const expected = `

The lists

-

Here is an unordered list:

-
  • Item 1
  • + const expected = `

    The lists

    Here is an unordered list:

    +
      +
    • Item 1
    • Item 2
    • -
    • Item 3
    - +
  • Item 3
  • +

Here is an ordered list:

-
  1. First
  2. +
      +
    1. First
    2. Second
    3. Third
    4. -
    `; +
+`; const rendered = parseMarkdownToReactEmailJSX({ markdown, @@ -106,7 +181,23 @@ Here is an ordered list: | Cell 3 | Cell 4 | `; const expected = `

A table example:

-
Column 1Column 2
Cell 1Cell 2
Cell 3Cell 4
`; + + + + + + + + + + + + + + + +
Column 1Column 2
Cell 1Cell 2
Cell 3Cell 4
+`; const rendered = parseMarkdownToReactEmailJSX({ markdown, @@ -125,11 +216,13 @@ greet("World") \`\`\``; const expected = `

A example:

-
  python
-  def greet(name):
-  print(f"Hello, {name}!")
-
greet("World") -
`; +
python
+def greet(name):
+print(f"Hello, {name}!")
+
+greet("World")
+
+`; const rendered = parseMarkdownToReactEmailJSX({ markdown, @@ -144,9 +237,10 @@ greet("World") const expected = `

A example:

-

Here's a block quote:

-

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

-
`; +

Here's a block quote: +Lorem ipsum dolor sit amet, consectetur adipiscing elit.

+ +`; const rendered = parseMarkdownToReactEmailJSX({ markdown, @@ -158,8 +252,9 @@ greet("World") const markdown = `A example: ![Image description](https://example.com/image.jpg)`; - const expected = `

A example:

-Image description`; + const expected = `

A example: +Image description

+`; const rendered = parseMarkdownToReactEmailJSX({ markdown, @@ -171,8 +266,9 @@ greet("World") const markdown = `A link example: Here's a link to [OpenAI's website](https://openai.com/).`; - const expected = `

A link example:

-

Here's a link to OpenAI's website.

`; + const expected = `

A link example: +Here's a link to OpenAI's website.

+`; const rendered = parseMarkdownToReactEmailJSX({ markdown, diff --git a/__tests__/reactEmailMarkdown.test.tsx b/__tests__/reactEmailMarkdown.test.tsx index 3f13eff..e701a5f 100644 --- a/__tests__/reactEmailMarkdown.test.tsx +++ b/__tests__/reactEmailMarkdown.test.tsx @@ -1,6 +1,6 @@ import React from "react"; import { render } from "@react-email/render"; -import { ReactEmailMarkdown } from "../src/components"; +import { ReactEmailMarkdown } from "../src"; describe("ReactEmailMarkdown component renders correctly", () => { it("renders the markdown in the correct format for browsers", () => { @@ -8,7 +8,7 @@ describe("ReactEmailMarkdown component renders correctly", () => { ); expect(actualOutput).toMatchInlineSnapshot( - `"

Hello, World!

"` + `"

Hello, World!

"` ); }); }); diff --git a/src/.DS_Store b/src/.DS_Store index d17bb8d765e21095653b1c827468bcdca04571cb..18d0551534822f1c6db19cb71a10e82b45473590 100644 GIT binary patch delta 35 rcmZoMXfc@J&&a(oU^g=(_hcTHxs&g+{M*dS_KR_1L)vC`j=%f>+y@NV delta 64 zcmZoMXfc@J&&azmU^g=(?_?g9xs!`oM1)Hiiam4klaq4tlNcBn1Q-|?yEo5b;bojG T&ML?RWOuQOGi_$)_{$FfMZysi diff --git a/src/components/reactEmailMarkdown.tsx b/src/components/reactEmailMarkdown.tsx index 21417e6..8fc5634 100644 --- a/src/components/reactEmailMarkdown.tsx +++ b/src/components/reactEmailMarkdown.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { StylesType } from "../types"; -import { parseMarkdownToReactEmailJSX } from "../utils"; +import { parseMarkdownToReactEmailJSX } from "../parseMarkdownToReactEmailJSX"; interface ReactEmailMarkdownProps { markdown: string; diff --git a/src/hooks.ts b/src/hooks.ts new file mode 100644 index 0000000..155388d --- /dev/null +++ b/src/hooks.ts @@ -0,0 +1,17 @@ +import DOMPurify from "isomorphic-dompurify"; + +DOMPurify.addHook("afterSanitizeAttributes", (node) => { + if ("target" in node) { + node.setAttribute("target", "_blank"); + } +}); + +function preprocess(markdown: string) { + return markdown; +} + +function postprocess(html: string): string { + return DOMPurify.sanitize(html); +} + +export const hooks = { preprocess, postprocess }; diff --git a/src/index.ts b/src/index.ts index 55d88fc..f72e9d1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,27 +1,15 @@ -/** - * Default Styles - */ -export { styles } from "./styles"; - -/** - * Default Matchers - */ -export { patterns } from "./patterns"; - /** * Types */ -export { Patterns, StylesType } from "./types"; +export { StylesType } from "./types"; /** - * Functions + * Utility Functions */ export { - camelToKebabCase, - parseCssInJsToInlineCss, - parseMarkdownToReactEmail, parseMarkdownToReactEmailJSX, -} from "./utils"; + ParseMarkdownToReactEmailJSXProps, +} from "./parseMarkdownToReactEmailJSX"; /** * Components diff --git a/src/marked.ts b/src/marked.ts deleted file mode 100644 index 1da3ceb..0000000 --- a/src/marked.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { marked, RendererObject } from "marked"; -import { StylesType } from "./types"; -// import { styles } from "./styles"; -import { CSSProperties } from "react"; -// import { parseCssInJsToInlineCss } from "./utils"; - -function camelToKebabCase(str: string): string { - return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(); -} - -function parseCssInJsToInlineCss( - cssProperties: CSSProperties | undefined -): string { - if (!cssProperties) return ""; - - return Object.entries(cssProperties) - .map(([property, value]) => `${camelToKebabCase(property)}:${value}`) - .join(";"); -} - -interface initRendererProps { - customStyles?: StylesType; - withDataAttr?: boolean; -} - -const initRenderer = ({ - customStyles, - withDataAttr = false, -}: initRendererProps): RendererObject => { - const finalStyles = { ...{}, ...customStyles }; - - const customRenderer: RendererObject = { - heading(text, level) { - return `${text}`; - }, - }; - - return customRenderer; -}; - -class MarkdownParser { - private readonly renderer: RendererObject; - - constructor({ - customStyles, - withDataAttr, - }: { - customStyles?: StylesType; - withDataAttr?: boolean; - }) { - this.renderer = initRenderer({ customStyles, withDataAttr }); - } - - parse(markdown: string) { - marked.use({ renderer: this.renderer }); - return marked.parse(markdown); - } -} - -const x = new MarkdownParser({ - customStyles: { h1: { fontWeight: "bold" } }, - withDataAttr: true, -}); -console.log(x.parse("# Hello World!")); - -/** - code(string code, string infostring, boolean escaped) -blockquote(string quote) -html(string html, boolean block) -hr() -list(string body, boolean ordered, number start) -listitem(string text, boolean task, boolean checked) -checkbox(boolean checked) -paragraph(string text) -table(string header, string body) -tablerow(string content) -tablecell(string content, object flags) -Inline-level renderer methods -strong(string text) -em(string text) -codespan(string code) -br() -del(string text) -link(string href, string title, string text) -image(string href, string title, string text) -text(string text) - */ diff --git a/src/parseMarkdownToReactEmailJSX.ts b/src/parseMarkdownToReactEmailJSX.ts new file mode 100644 index 0000000..de12b3e --- /dev/null +++ b/src/parseMarkdownToReactEmailJSX.ts @@ -0,0 +1,17 @@ +import { MarkdownParser } from "./parser"; +import { StylesType } from "./types"; + +export type ParseMarkdownToReactEmailJSXProps = { + markdown: string; + customStyles?: StylesType; + withDataAttr?: boolean; +}; + +export const parseMarkdownToReactEmailJSX = ({ + markdown, + customStyles, + withDataAttr, +}: ParseMarkdownToReactEmailJSXProps) => { + const parser = new MarkdownParser({ customStyles, withDataAttr }); + return parser.parse(markdown); +}; diff --git a/src/parser.ts b/src/parser.ts new file mode 100644 index 0000000..e91c420 --- /dev/null +++ b/src/parser.ts @@ -0,0 +1,27 @@ +import { marked, RendererObject } from "marked"; +import { StylesType } from "./types"; +import { initRenderer } from "./utils"; +import { hooks } from "./hooks"; + +export class MarkdownParser { + private readonly renderer: RendererObject; + + constructor({ + customStyles, + withDataAttr, + }: { + customStyles?: StylesType; + withDataAttr?: boolean; + }) { + this.renderer = initRenderer({ customStyles, withDataAttr }); + } + + parse(markdown: string) { + marked.use({ + renderer: this.renderer, + hooks, + }); + + return marked.parse(markdown); + } +} diff --git a/src/patterns.ts b/src/patterns.ts deleted file mode 100644 index 77a4bca..0000000 --- a/src/patterns.ts +++ /dev/null @@ -1,25 +0,0 @@ -export const patterns = { - h1: /^#\s+(.+)$/gm, - h2: /^##\s+(.+)$/gm, - h3: /^###\s+(.+)$/gm, - h4: /^####\s+(.+)$/gm, - h5: /^#####\s+(.+)$/gm, - h6: /^######\s+(.+)$/gm, - p: /^(?!#{1,6}\s)((?!<(pre|blockquote|Text)\b[^>]*>)(?!.*<\/(pre|blockquote|Text)>$)((?!(?:[-*+\s]+|\d+\.\s+|#\s+|.*?\|.*?\||!\[.*?\]\(.*?\)|```\s*\n(?:.|\n)+?```| {4}(?:.|\n)+?))(?:.|\n)+?))(?=\n{2,}|$)/gm, - bold: /\*\*(.+?)\*\*/g, - italic: /([*_])(.*?)\1/g, - li: /^\s*[-|\*]\s+(.*)$/gm, - ul: /()(?![\s\S]*<\/ul>)/gs, - ol: /^(?:\d+\.\s+.+$(?:\n(?!$).+)*(?:\n|$))+/gm, - image: /!\[(.*?)\]\((.*?)\)/g, - link: /\[(.+?)\]\((.*?)\)/g, - blockQuote: /^>(?: .+?(?:\n|$))+/gm, - nestedBlockQuote: /^>( .+?(?:\n|$))+/gm, - codeBlocks: /```(?:[\s\S]*?\n)?([\s\S]*?)\n```/g, - codeInline: /(? { - if ("target" in node) { - node.setAttribute("target", "_blank"); - } -}); +import { StylesType, initRendererProps } from "./types"; +import { RendererObject } from "marked"; +import { styles } from "./styles"; export function camelToKebabCase(str: string): string { return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(); @@ -20,625 +12,246 @@ export function parseCssInJsToInlineCss( ): string { if (!cssProperties) return ""; + const numericalCssProperties = [ + "width", + "height", + "margin", + "marginTop", + "marginRight", + "marginBottom", + "marginLeft", + "padding", + "paddingTop", + "paddingRight", + "paddingBottom", + "paddingLeft", + "borderWidth", + "borderTopWidth", + "borderRightWidth", + "borderBottomWidth", + "borderLeftWidth", + "outlineWidth", + "top", + "right", + "bottom", + "left", + "fontSize", + "lineHeight", + "letterSpacing", + "wordSpacing", + "maxWidth", + "minWidth", + "maxHeight", + "minHeight", + "borderRadius", + "borderTopLeftRadius", + "borderTopRightRadius", + "borderBottomLeftRadius", + "borderBottomRightRadius", + "textIndent", + "gridColumnGap", + "gridRowGap", + "gridGap", + "translateX", + "translateY", + ]; + return Object.entries(cssProperties) - .map(([property, value]) => `${camelToKebabCase(property)}:${value}`) + .map(([property, value]) => { + if ( + typeof value === "number" && + numericalCssProperties.includes(property) + ) { + return `${camelToKebabCase(property)}:${value}px`; + } else { + return `${camelToKebabCase(property)}:${value}`; + } + }) .join(";"); } -export function parseMarkdownToReactEmail( - markdown: string, - customStyles?: StylesType -): string { +export const initRenderer = ({ + customStyles, + withDataAttr = false, +}: initRendererProps): RendererObject => { const finalStyles = { ...styles, ...customStyles }; - let reactMailTemplate = ""; - // Handle inline code (e.g., `code`) - reactMailTemplate = markdown.replace( - patterns.codeInline, - `$2` - ); - - // Handle code blocks (e.g., ```code```) - reactMailTemplate = reactMailTemplate.replace( - patterns.codeBlocks, - function (_, codeContent: string) { - const indentedCodeContent = codeContent - .split("\n") - .map((line) => ` ${line}`) - .join("\n"); - return `\n${indentedCodeContent}\n`; - } - ); + }>\n${quote}\n`; + }, - // Handle blockquotes - function parseMarkdownWithBlockQuotes(markdown: string): string { - const blockquoteRegex = /^(>\s*((?:.+\n?)+))(?!\n(?=>\s))/gm; - - function parseBlockQuote(match: string) { - const nestedContent = match.replace(/^>\s*/gm, ""); - const nestedHTML = parseMarkdownWithBlockQuotes(nestedContent); - return `\n${nestedHTML}\n`; - } - - return markdown.replace(blockquoteRegex, parseBlockQuote); - } - - reactMailTemplate = parseMarkdownWithBlockQuotes(reactMailTemplate); - - // Handle paragraphs - reactMailTemplate = reactMailTemplate.replace( - patterns.p, - `$1` - ); - - // Handle headings (e.g., # Heading) - reactMailTemplate = reactMailTemplate.replace( - patterns.h1, - `$1` - ); - reactMailTemplate = reactMailTemplate.replace( - patterns.h2, - `$1` - ); - reactMailTemplate = reactMailTemplate.replace( - patterns.h3, - `$1` - ); - reactMailTemplate = reactMailTemplate.replace( - patterns.h4, - `$1` - ); - reactMailTemplate = reactMailTemplate.replace( - patterns.h5, - `$1` - ); - reactMailTemplate = reactMailTemplate.replace( - patterns.h6, - `$1` - ); - - // Handle Tables from GFM - reactMailTemplate = reactMailTemplate.replace( - patterns.table, - (match: string) => { - const rows = match.trim().split("\n"); - const headers = rows[0] - .split("|") - .slice(1, -1) - .map((cell) => cell.trim()); - const alignments = rows[1] - .split("|") - .slice(1, -1) - .map((cell) => { - const align = cell.trim().toLowerCase(); - return align === ":--" - ? "left" - : align === "--:" - ? "right" - : "center"; - }); - const body = rows - .slice(2) - .map((row) => { - const cells = row - .split("|") - .slice(1, -1) - .map((cell) => cell.trim()); - return `${cells - .map( - (cell, index) => - `${cell}` - ) - .join("")}`; - }) - .join(""); - - const table = `${headers - .map( - (header, index) => - `${header}` - ) - .join("")}${body}`; - return table; - } - ); - - // Handle strikethrough - reactMailTemplate = reactMailTemplate.replace( - patterns.strikethrough, - `$1` - ); - - // Handle bold text (e.g., **bold**) - reactMailTemplate = reactMailTemplate.replace( - patterns.bold, - `$1` - ); - - // Handle italic text (e.g., *italic*) - reactMailTemplate = reactMailTemplate.replace( - patterns.italic, - `$2` - ); - - // Handle lists (unordered) - reactMailTemplate = reactMailTemplate.replace( - patterns.li, - `$1` - ); - reactMailTemplate = reactMailTemplate.replace( - patterns.ul, - `$&` - ); - - // Handle lists (ordered) - reactMailTemplate = reactMailTemplate.replace(patterns.ol, function (match) { - const listItems = match - .split("\n") - .map((line) => { - const listItemContent = line.replace(/^\d+\.\s+/, ""); - return listItemContent - ? `${listItemContent}` - : ""; - }) - .join("\n"); - return `${listItems}`; - }); - - // Handle images (e.g., ![alt text](url)) - reactMailTemplate = reactMailTemplate.replace( - patterns.image, - `$1` - ); - - // Handle links (e.g., [link text](url)) - reactMailTemplate = reactMailTemplate.replace( - patterns.link, - `$1` - ); - - // Handle line breaks (e.g.,
) - reactMailTemplate = reactMailTemplate.replace( - patterns.br, - `` - ); - - // Handle horizontal rules (e.g., ---) - reactMailTemplate = reactMailTemplate.replace( - patterns.hr, - `` - ); - - reactMailTemplate = `
${reactMailTemplate}
`; - - return reactMailTemplate; -} - -interface ParseMarkdownToReactEmailJSXProps { - markdown: string; - customStyles?: StylesType; - withDataAttr?: boolean; -} + } />`; + }, -export function parseMarkdownToReactEmailJSX({ - markdown, - customStyles, - withDataAttr = false, -}: ParseMarkdownToReactEmailJSXProps): string { - if ( - markdown === undefined || - markdown === null || - markdown === "" || - typeof markdown !== "string" - ) { - return ""; - } + code(code) { + code = code.replace(/\n$/, "") + "\n"; - const finalStyles = { ...styles, ...customStyles }; - let reactMailTemplate = ""; - - // Handle inline code (e.g., `code`) - reactMailTemplate = markdown.replace( - patterns.codeInline, - `$2` - ); - - // Handle code blocks (e.g., ```code```) - reactMailTemplate = reactMailTemplate.replace( - patterns.codeBlocks, - function (_, codeContent: string) { - const indentedCodeContent = codeContent - .split("\n") - .map((line) => ` ${line}`) - .join("\n"); return `\n${indentedCodeContent}\n`; - } - ); + }>${code}\n`; + }, - // Handle blockquotes - function parseMarkdownWithBlockQuotes(markdown: string): string { - const blockquoteRegex = /^(>\s*((?:.+\n?)+))(?!\n(?=>\s))/gm; + codespan(text) { + return `${text}`; + }, - function parseBlockQuote(match: string) { - const nestedContent = match.replace(/^>\s*/gm, ""); - const nestedHTML = parseMarkdownWithBlockQuotes(nestedContent); - return `\n${nestedHTML}\n`; - } + }>${text}`; + }, - return markdown.replace(blockquoteRegex, parseBlockQuote); - } + em(text) { + return `${text}`; + }, + + heading(text, level) { + return `${text}`; + }, + + hr() { + return `\n`; + }, - reactMailTemplate = parseMarkdownWithBlockQuotes(reactMailTemplate); + image(href, _, text) { + let out = `${text}`; + return out; + }, + + link(href, _, text) { + let out = `${text}`; + return out; + }, + + list(body, ordered, start) { + const type = ordered ? "ol" : "ul"; + const startatt = ordered && start !== 1 ? ' start="' + start + '"' : ""; + return ( + "<" + + type + + startatt + + `${ + parseCssInJsToInlineCss(finalStyles.ol) !== "" + ? ` style="${parseCssInJsToInlineCss( + finalStyles[ordered ? "ol" : "ul"] + )}"` + : "" + } >\n` + + body + + "\n" + ); + }, + + listitem(text) { + return `${text}\n`; + }, - // Handle paragraphs - reactMailTemplate = reactMailTemplate.replace( - patterns.p, - `$1

` - ); + paragraph(text) { + return `${text}

\n`; + }, - // Handle headings (e.g., # Heading) - reactMailTemplate = reactMailTemplate.replace( - patterns.h1, - `$1` - ); - reactMailTemplate = reactMailTemplate.replace( - patterns.h2, - `$1` - ); - reactMailTemplate = reactMailTemplate.replace( - patterns.h3, - `$1` - ); - reactMailTemplate = reactMailTemplate.replace( - patterns.h4, - `$1` - ); - reactMailTemplate = reactMailTemplate.replace( - patterns.h5, - `$1` - ); - reactMailTemplate = reactMailTemplate.replace( - patterns.h6, - `$1` - ); + strong(text) { + return `${text}`; + }, - // Handle Tables from GFM - reactMailTemplate = reactMailTemplate.replace( - patterns.table, - (match: string) => { - const rows = match.trim().split("\n"); - const headers = rows[0] - .split("|") - .slice(1, -1) - .map((cell) => cell.trim()); - const alignments = rows[1] - .split("|") - .slice(1, -1) - .map((cell) => { - const align = cell.trim().toLowerCase(); - return align === ":--" - ? "left" - : align === "--:" - ? "right" - : "center"; - }); - const body = rows - .slice(2) - .map((row) => { - const cells = row - .split("|") - .slice(1, -1) - .map((cell) => cell.trim()); - return `${cells - .map( - (cell, index) => - `${cell}` - ) - .join("")}`; - }) - .join(""); + table(header, body) { + if (body) body = `${body}`; - const table = `\n\n${header}\n${body}\n`; + }, + + tablecell(content, flags) { + const type = flags.header ? "th" : "td"; + const tag = flags.align + ? `<${type} align="${flags.align}"${ + parseCssInJsToInlineCss(finalStyles.td) !== "" + ? ` style="${parseCssInJsToInlineCss(finalStyles.td)}"` + : "" + }>` + : `<${type}${ + parseCssInJsToInlineCss(finalStyles.td) !== "" + ? ` style="${parseCssInJsToInlineCss(finalStyles.td)}"` + : "" + }>`; + return tag + content + `\n`; + }, + + tablerow(content) { + return `${headers - .map( - (header, index) => - `${header}` - ) - .join("")}${body}`; - return table; - } - ); - - // Handle strikethrough - reactMailTemplate = reactMailTemplate.replace( - patterns.strikethrough, - `$1` - ); - - // Handle bold text (e.g., **bold**) - reactMailTemplate = reactMailTemplate.replace( - patterns.bold, - `$1` - ); + }>\n${content}\n`; + }, + }; - // Handle italic text (e.g., *italic*) - reactMailTemplate = reactMailTemplate.replace( - patterns.italic, - `$2` - ); - - // Handle lists (unordered) - reactMailTemplate = reactMailTemplate.replace( - patterns.li, - `$1` - ); - reactMailTemplate = reactMailTemplate.replace( - patterns.ul, - `$&` - ); - - // Handle lists (ordered) - reactMailTemplate = reactMailTemplate.replace(patterns.ol, function (match) { - const listItems = match - .split("\n") - .map((line) => { - const listItemContent = line.replace(/^\d+\.\s+/, ""); - return listItemContent - ? `${listItemContent}` - : ""; - }) - .join("\n"); - return `${listItems}`; - }); - - // Handle images (e.g., ![alt text](url)) - reactMailTemplate = reactMailTemplate.replace( - patterns.image, - `$1` - ); - - // Handle links (e.g., [link text](url)) - reactMailTemplate = reactMailTemplate.replace( - patterns.link, - `$1` - ); - - // Handle line breaks (e.g.,
) - reactMailTemplate = reactMailTemplate.replace( - patterns.br, - `` - ); - - // Handle horizontal rules (e.g., ---) - reactMailTemplate = reactMailTemplate.replace( - patterns.hr, - `` - ); - - return DOMPurify.sanitize(reactMailTemplate, { - USE_PROFILES: { html: true }, - }); -} + return customRenderer; +}; From 3ae845cf70a5c4294cc9cd8504ac6048ae521d53 Mon Sep 17 00:00:00 2001 From: lordelogos Date: Fri, 25 Aug 2023 11:11:50 +0100 Subject: [PATCH 3/8] wip: clean up for release --- .changeset/fast-bobcats-battle.md | 15 +++++++ README.md | 16 +------ pre-planning/BASE-STYLES.md | 68 ----------------------------- pre-planning/IDEAS.md | 5 --- pre-planning/MATCHER-CASES.md | 50 --------------------- pre-planning/PROJECT-BREAKDOWN.md | 55 ----------------------- pre-planning/PROJECT-OUTLINE.md | 35 --------------- pre-planning/REPLACEMENT-CASES.md | 43 ------------------ pre-planning/TODOS.md | 3 -- src/index.ts | 12 ++--- src/parseMarkdownToReactEmailJSX.ts | 8 +--- src/types.ts | 6 +++ 12 files changed, 31 insertions(+), 285 deletions(-) create mode 100644 .changeset/fast-bobcats-battle.md delete mode 100644 pre-planning/BASE-STYLES.md delete mode 100644 pre-planning/IDEAS.md delete mode 100644 pre-planning/MATCHER-CASES.md delete mode 100644 pre-planning/PROJECT-BREAKDOWN.md delete mode 100644 pre-planning/PROJECT-OUTLINE.md delete mode 100644 pre-planning/REPLACEMENT-CASES.md delete mode 100644 pre-planning/TODOS.md diff --git a/.changeset/fast-bobcats-battle.md b/.changeset/fast-bobcats-battle.md new file mode 100644 index 0000000..b6860a2 --- /dev/null +++ b/.changeset/fast-bobcats-battle.md @@ -0,0 +1,15 @@ +--- +"md-to-react-email": major +--- + +### Rewrite + +### Changes + +- Added [`Marked`]() for markdown transformations +- Removed `ParseMarkdownToReactEmail` function + +### Fixes + +- Fixed issue with parsing list +- Fixed `parseCssInJsToInlineCss` issue with numerical values diff --git a/README.md b/README.md index 29a9861..bd1a1aa 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,13 @@ Read the documentation [here](https://md2re.codeskills.dev/) md-to-react-email is a lightweight utility for converting [Markdown](https://www.markdownguide.org/) into valid [React-email](https://react.email) templates. This tool simplifies the process of creating responsive and customizable email templates by leveraging the power of React and Markdown. +**Note**: Starting from [v4](), `md-to-react-email` uses [`Marked`] for markdown transformation. see all changes [here]() + ### Support The following markdown flavors are supported - Offical markdown flavour -- Github flavoured markdown ## Installation @@ -35,7 +36,6 @@ npm install md-to-react-email - `camelToKebabCase`: converts strings from camelcase ['thisIsCamelCase'] to kebab case ['this-is-kebab-case'] - `parseCssInJsToInlineCss`: converts css styles from css-in-js to inline css e.g fontSize: "18px" => font-size: 18px; -- `parseMarkdownToReactEmail`: parses markdown to a valid react-email string that can be copied and pasted directly into your codebase - `parseMarkdownToReactEmailJSX`: parses markdown to valid react-email JSX for the client (i.e the browser) ### Components: @@ -75,18 +75,6 @@ npm install md-to-react-email ``` -- For code generation (copy and paste) - - ``` - import {parseMarkdownToReactEmail} from "md-to-react-email" - - const markdown = `# Hello World` - const parsedReactMail = parseMarkdownToReactEmail(markdown) - - console.log(parsedReactMail) // `` - - ``` - ## Components md-to-react-email contains pre-defined react-email and html components for the email template structure and styling. You can modify these components to customize the look and feel of your email template. diff --git a/pre-planning/BASE-STYLES.md b/pre-planning/BASE-STYLES.md deleted file mode 100644 index 7a41987..0000000 --- a/pre-planning/BASE-STYLES.md +++ /dev/null @@ -1,68 +0,0 @@ -h1, h2, h3, h4, h5, h6 { -font-weight: 500; -padding-top: 20px; -} - -h1 { -font-size: 2.5rem; -} - -h2 { -font-size: 2rem; -} - -h3 { -font-size: 1.75rem; -} - -.h4, h4 { -font-size: 1.5rem; -} - -.h5, h5 { -font-size: 1.25rem; -} - -.h6, h6 { -font-size: 1rem; -} - -bold{ -font-weight: bold; -} - -italic { -font-style: italic; -} - -blockquote { -background: #f9f9f9; -border-left: 10px solid #ccc; -margin: 1.5em 10px; -padding: 1em 10px 0.1em 10px; -quotes: "\201C""\201D""\2018""\2019"; -} - -code { -color: #212529; -font-size: 87.5%; -word-wrap: break-word; -display: inline; -background: #f8f8f8; -font-family: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; -} - -code-block { -padding-top: 10px; -padding-right: 10px; -padding-left: 10px; -padding-bottom: 1px; -margin-bottom: 20px; -background: #f8f8f8; -} - -link { -color: #007bff; -text-decoration: underline; -background-color: transparent; -} diff --git a/pre-planning/IDEAS.md b/pre-planning/IDEAS.md deleted file mode 100644 index 687c57e..0000000 --- a/pre-planning/IDEAS.md +++ /dev/null @@ -1,5 +0,0 @@ -- export package for use on web ui -- export directly as reactmail component ✅ -- pass custom components for different md tags -- pass custom styles for different md tags ✅ -- custom matchers for md tags for different flavors diff --git a/pre-planning/MATCHER-CASES.md b/pre-planning/MATCHER-CASES.md deleted file mode 100644 index 537cc31..0000000 --- a/pre-planning/MATCHER-CASES.md +++ /dev/null @@ -1,50 +0,0 @@ -// Handle headings (e.g., # Heading) -(/^#\s+(.+)$/gm, "

$1

"); - (/^##\s+(.+)$/gm, "

$1

"); - (/^###\s+(.+)$/gm, "

$1

"); - (/^####\s+(.+)$/gm, "

$1

"); - (/^#####\s+(.+)$/gm, "
$1
"); - (/^######\s+(.+)$/gm, "
$1
"); - -// Handle paragraphs -( -/((\n|^)(?!\n)((?!<\/?(h|ul|ol|p|pre|div|blockquote)[>\s]).)+(\n|$)+)+/gm, - "

$&

" -); - -// Handle bold text (e.g., **bold**) -(/\*\*(.+?)\*\*/g, "$1"); - -// Handle italic text (e.g., _italic_) -(/\*(.+?)\*/g, "$1"); - -// Handle unordered lists -(/^\s*-\s+(.*)$/gm, "
  • $1
  • "); - (/<\/li>\n
  • /g, "
  • "); -`
      ${markdown}
    `; - -// Handle ordered lists -(/^\s*\d+\.\s+(.*)$/gm, "
  • $1
  • "); - (/<\/li>\n
  • /g, "
  • "); -`
      ${markdown}
    `; - -// Handle links (e.g., [link text](url)) -(/\[(.+?)\]\((.\*?)\)/g, '$1'); - -// Handle images (e.g., ![alt text](url)) -( -/!\[(._?)\]\((._?)\)/g, -'$1' -); - -// Handle code blocks (e.g., `code`) -(/`(.+?)`/gs, "
    $1
    "); - -// Handle inline code (e.g., `code`) -(/`(.+?)`/g, "$1"); - -// Handle line breaks (e.g.,
    ) -(/ \n/g, "
    "); - -// Handle horizontal rules (e.g., ---) -(/^-{3,}$/gm, "
    "); diff --git a/pre-planning/PROJECT-BREAKDOWN.md b/pre-planning/PROJECT-BREAKDOWN.md deleted file mode 100644 index 0da0b1a..0000000 --- a/pre-planning/PROJECT-BREAKDOWN.md +++ /dev/null @@ -1,55 +0,0 @@ -# Project breakdown - -## Functions: - -- `camelToKebabCase`: converts strings from camelcase ['thisIsCamelCase'] to kebab case ['this-is-kebab-case'] -- `parseCssInJsToInlineCss`: converts css styles from css-in-js to inline css e.g fontSize: "18px" => font-size: 18px; -- `parseMarkdownToReactEmail`: parses markdown to a valid react-email string that can be copied and pasted directly into your codebase -- `parseMarkdownToReactEmailJSX`: parses markdown to valid react-email JSX for the client (i.e the browser) - -## Components: - -- `ReactEmailMarkdown`: a react-email component that takes in markdown input and parses it directly in your code base - -## Usage: - -- Directly as `React-email` component - -``` -import {ReactEmailMarkdown} from "md-to-react-email" - -export default function EmailTemplate() { - return ( - - -
    - -
    -
    - ) - } -``` - -- Directly into react-email template - -``` -import {parseMarkdownToReactEmailJSX} from "md-to-react-email" - -const markdown = `# Hello World` -const parsedReactMail = parseMarkdownToReactEmail(markdown) - -console.log(parsedReactMail) // `` - -``` - -- For code generation (copy and paste) - -``` -import {parseMarkdownToReactEmail} from "md-to-react-email" - -const markdown = `# Hello World` -const parsedReactMail = parseMarkdownToReactEmail(markdown) - -console.log(parsedReactMail) // `` - -``` diff --git a/pre-planning/PROJECT-OUTLINE.md b/pre-planning/PROJECT-OUTLINE.md deleted file mode 100644 index 76964ac..0000000 --- a/pre-planning/PROJECT-OUTLINE.md +++ /dev/null @@ -1,35 +0,0 @@ -# Project breakdown - -### Note: This is a non opinionated project. - -- Create some basic styles for all our components - - - Headers h1-h6 - - Paragraphs - - Quotes - - Lists - ordered and unordered - - Code Blocks - - Images - - Horizontal Rules - - Line breaks - - Links - -- ReactMail (rm) -- Input markdown (md) -- Function mdToRm -- Output valid RM e.g (# A boy => A boy) - -# General file structure - -- Styles file(css-in-js): contains all the basic styles - - > CSS
    - > .header{
    - > font-size: 16px
    - > }
    >
    - > CSS-in-JS
    - > const header = {
    - > fontSize: 16
    - > }
    - -- Index.js: one function that takes in md and spits out rm with styles already in place diff --git a/pre-planning/REPLACEMENT-CASES.md b/pre-planning/REPLACEMENT-CASES.md deleted file mode 100644 index bd8082d..0000000 --- a/pre-planning/REPLACEMENT-CASES.md +++ /dev/null @@ -1,43 +0,0 @@ -``` - - - - - - - -
    - - - - - - -
    - - -``` - -- Headers h1-h6 - -- Paragraphs - -- Quotes -
    - -- Lists - ordered and unordered -- Inline code - -- Code Blocks -
    - -- Images - -- Horizontal Rules -
    -- Line breaks -
    -- Links - diff --git a/pre-planning/TODOS.md b/pre-planning/TODOS.md deleted file mode 100644 index 3795581..0000000 --- a/pre-planning/TODOS.md +++ /dev/null @@ -1,3 +0,0 @@ -- Extensive testing of packages and components -- Create NPM package -- Export component directly into RM teplate✅ diff --git a/src/index.ts b/src/index.ts index f72e9d1..cec4b0f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,17 +1,19 @@ /** * Types */ -export { StylesType } from "./types"; +export { StylesType, ParseMarkdownToReactEmailJSXProps } from "./types"; /** * Utility Functions */ -export { - parseMarkdownToReactEmailJSX, - ParseMarkdownToReactEmailJSXProps, -} from "./parseMarkdownToReactEmailJSX"; +export { parseMarkdownToReactEmailJSX } from "./parseMarkdownToReactEmailJSX"; /** * Components */ export { ReactEmailMarkdown } from "./components"; + +/** + * String Utils + */ +export { camelToKebabCase, parseCssInJsToInlineCss } from "./utils"; diff --git a/src/parseMarkdownToReactEmailJSX.ts b/src/parseMarkdownToReactEmailJSX.ts index de12b3e..266be00 100644 --- a/src/parseMarkdownToReactEmailJSX.ts +++ b/src/parseMarkdownToReactEmailJSX.ts @@ -1,11 +1,5 @@ import { MarkdownParser } from "./parser"; -import { StylesType } from "./types"; - -export type ParseMarkdownToReactEmailJSXProps = { - markdown: string; - customStyles?: StylesType; - withDataAttr?: boolean; -}; +import { ParseMarkdownToReactEmailJSXProps } from "./types"; export const parseMarkdownToReactEmailJSX = ({ markdown, diff --git a/src/types.ts b/src/types.ts index f75d834..198ef1b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -33,3 +33,9 @@ export type initRendererProps = { customStyles?: StylesType; withDataAttr?: boolean; }; + +export type ParseMarkdownToReactEmailJSXProps = { + markdown: string; + customStyles?: StylesType; + withDataAttr?: boolean; +}; From 620a2eb2dce6cf9f169a1a375d8e2644e2d50edf Mon Sep 17 00:00:00 2001 From: Paul Ehikhuemen <67395687+lordelogos@users.noreply.github.com> Date: Fri, 25 Aug 2023 11:14:36 +0100 Subject: [PATCH 4/8] Update fast-bobcats-battle.md --- .changeset/fast-bobcats-battle.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.changeset/fast-bobcats-battle.md b/.changeset/fast-bobcats-battle.md index b6860a2..261c903 100644 --- a/.changeset/fast-bobcats-battle.md +++ b/.changeset/fast-bobcats-battle.md @@ -2,14 +2,12 @@ "md-to-react-email": major --- -### Rewrite - ### Changes -- Added [`Marked`]() for markdown transformations +- Added [`Marked`](https://marked.js.org/) for markdown transformations - Removed `ParseMarkdownToReactEmail` function ### Fixes -- Fixed issue with parsing list +- Fixed issue with [list parsing](https://github.com/codeskills-dev/md-to-react-email/issues/11) - Fixed `parseCssInJsToInlineCss` issue with numerical values From 05cb52ba2d372af2f691fab34ab9176388b4d609 Mon Sep 17 00:00:00 2001 From: lordelogos Date: Fri, 25 Aug 2023 11:17:37 +0100 Subject: [PATCH 5/8] feat: v4 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bd1a1aa..9573bc3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Read the documentation [here](https://md2re.codeskills.dev/) md-to-react-email is a lightweight utility for converting [Markdown](https://www.markdownguide.org/) into valid [React-email](https://react.email) templates. This tool simplifies the process of creating responsive and customizable email templates by leveraging the power of React and Markdown. -**Note**: Starting from [v4](), `md-to-react-email` uses [`Marked`] for markdown transformation. see all changes [here]() +**Note**: Starting from `version 4`, `md-to-react-email` uses [`Marked`](https://marked.js.org/) for markdown transformation. see all changes [here](/CHANGELOG.md) ### Support From d2859ea590eeff6de85ed69a53cff773daa8e77d Mon Sep 17 00:00:00 2001 From: lordelogos Date: Fri, 25 Aug 2023 11:19:17 +0100 Subject: [PATCH 6/8] feat: v4 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9573bc3..b5ff42b 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ The following components are available for customization: - Text: paragraphs, bold and italic text - Links - Code: Code blocks and inline code -- Lists: ul, li +- Lists: ul, ol, li - Image - Line-breaks (br) - Horizontal-rule (hr) From d99f1fe602d745155946076c536205bcb0e50178 Mon Sep 17 00:00:00 2001 From: lordelogos Date: Fri, 25 Aug 2023 11:20:40 +0100 Subject: [PATCH 7/8] feat: updated package json --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index aaedfc8..fb19b21 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ ], "scripts": { "build": "tsup", - "demo": "npx tsc src/demo && node src/demo", "lint": "tsc", "release": "pnpm run build && changeset publish", "test": "jest --coverage", @@ -52,7 +51,7 @@ }, "dependencies": { "isomorphic-dompurify": "1.7.0", - "marked": "^7.0.4" + "marked": "7.0.4" }, "publishConfig": { "access": "public" From 0a29db8993dba114d1a32f7f3b3514ceac2f83ce Mon Sep 17 00:00:00 2001 From: lordelogos Date: Fri, 25 Aug 2023 11:22:38 +0100 Subject: [PATCH 8/8] feat: updated lockfile --- pnpm-lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dfdb15f..8d52b74 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,7 +5,7 @@ dependencies: specifier: 1.7.0 version: 1.7.0 marked: - specifier: ^7.0.4 + specifier: 7.0.4 version: 7.0.4 devDependencies: