simple-customize-markdown-converter 1.0.6 → 1.1.0

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.
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const react_1 = __importDefault(require("react"));
7
+ class ReactRenderer {
8
+ constructor(footNoteResolver, options) {
9
+ this.footNoteResolver = footNoteResolver;
10
+ this.options = options;
11
+ }
12
+ /**
13
+ * Render a Node (AST) to a ReactNode according renderer options
14
+ *
15
+ * @param node - The abstract syntax tree (AST) from the Parser
16
+ * @returns The ReactNode representing the rendered Markdown element.
17
+ */
18
+ render(node) {
19
+ //Get proper handler type
20
+ const handler = this.handleRender(node.type);
21
+ //If node have children, recursive to handle all node's children
22
+ const children = "children" in node ? node.children.map((ele) => this.render(ele)) : [];
23
+ return handler(node, children);
24
+ }
25
+ /**
26
+ * Select the appropriate rendering handler for a specific node type
27
+ * @param type - The type of AST Note
28
+ * @returns A function take a node and its children to procude a ReactNode.
29
+ */
30
+ handleRender(type) {
31
+ const defaultRender = {
32
+ //Base structural nodes
33
+ Document: (_node, children) => react_1.default.createElement(react_1.default.Fragment, null, ...children, this.renderFootnotes()),
34
+ Paragraph: (_node, children) => react_1.default.createElement("p", null, ...children),
35
+ //Container nodes
36
+ CodeBlock: (node) => react_1.default.createElement("pre", null, react_1.default.createElement("code", { className: `lang-${node.lang}` }, node.content)),
37
+ Header: (node, children) => react_1.default.createElement(`h${node.level}`, { style: { borderBottom: node.level <= 2 ? "1px solid #d1d9e0b3" : undefined } }, ...children),
38
+ Quote: (_node, children) => react_1.default.createElement("blockquote", { style: { margin: "0", padding: "0 1em", color: "#59636e", borderLeft: ".25em solid #d1d9e0" } }, ...children),
39
+ //For list nodes
40
+ List: (node, children) => node.ordered ?
41
+ react_1.default.createElement("ol", null, ...children) :
42
+ react_1.default.createElement("ul", null, ...children),
43
+ ListItem: (_node, children) => react_1.default.createElement("li", null, ...children),
44
+ TaskItem: (node, children) => react_1.default.createElement("li", null, react_1.default.createElement("input", {
45
+ type: "checkbox",
46
+ disabled: true,
47
+ checked: !!node.checked,
48
+ readOnly: true
49
+ }), ...children),
50
+ //Styling nodes
51
+ Bold: (_node, children) => react_1.default.createElement("strong", null, ...children),
52
+ Italic: (_node, children) => react_1.default.createElement("em", null, ...children),
53
+ Strikethrough: (_node, children) => react_1.default.createElement("s", null, ...children),
54
+ InlineCode: (node) => react_1.default.createElement("code", null, node.content),
55
+ //Media nodes
56
+ Link: (node) => react_1.default.createElement("a", {
57
+ href: node.href,
58
+ //Security reason
59
+ target: "_blank",
60
+ rel: "noopener"
61
+ }, node.text),
62
+ Image: (node) => react_1.default.createElement("img", {
63
+ src: node.src,
64
+ alt: node.alt,
65
+ }, null),
66
+ //Leaf nodes
67
+ HorizontalLine: (_node) => react_1.default.createElement("hr"),
68
+ Text: (node) => node.value || "",
69
+ //For table nodes
70
+ Table: (node, children) => this.renderTable(node, children),
71
+ //For HTML
72
+ HTMLBlock: (node) => this.options.converterOptions?.allowDangerousHtml
73
+ ? react_1.default.createElement("div", { dangerouslySetInnerHTML: { __html: node.value } })
74
+ : react_1.default.createElement("code", null, node.value),
75
+ HTMLInline: (node) => this.options.converterOptions?.allowDangerousHtml
76
+ ? react_1.default.createElement("span", { dangerouslySetInnerHTML: { __html: node.value } })
77
+ : react_1.default.createElement("code", null, node.value),
78
+ //For footnote
79
+ FootnoteRef: (node) => {
80
+ const idx = this.footNoteResolver.getUsedRefById(node.id);
81
+ return react_1.default.createElement("sup", { id: `fnref:${idx}` }, react_1.default.createElement("a", { href: `#fn:${idx}`, className: "footnote-ref" }, `[${idx}]`));
82
+ }
83
+ };
84
+ return (this.options.renderOptions?.elements?.[type] ?? defaultRender[type]);
85
+ }
86
+ renderTable(node, children) {
87
+ if (node.type === "Table") {
88
+ const header = node.rows.filter(row => row.isHeader);
89
+ const body = node.rows.filter(row => !row.isHeader);
90
+ const renderRows = (row, rowIndex) => {
91
+ return react_1.default.createElement("tr", { key: rowIndex }, row.cells.map((cell, cellIndex) => {
92
+ const tag = row.isHeader ? "th" : "td";
93
+ return react_1.default.createElement(tag, { key: cellIndex, style: { textAlign: cell.align } }, ...cell.children.map(c => this.render(c)));
94
+ }));
95
+ };
96
+ const tHead = header.length
97
+ ? react_1.default.createElement("thead", null, header.map((row, i) => renderRows(row, i)))
98
+ : null;
99
+ const tBody = body.length
100
+ ? react_1.default.createElement("tbody", null, body.map((row, i) => renderRows(row, i)))
101
+ : null;
102
+ return react_1.default.createElement("table", null, tHead, tBody);
103
+ }
104
+ else
105
+ return react_1.default.createElement("p", null, ...children);
106
+ }
107
+ renderFootnotes() {
108
+ if (this.footNoteResolver.isResolverValid()) {
109
+ const used = this.footNoteResolver.getUsedRef();
110
+ if (used.length === 0)
111
+ return null;
112
+ const items = used.map((id, i) => {
113
+ const def = this.footNoteResolver.getDef(id) ?? "";
114
+ const idx = i + 1;
115
+ return react_1.default.createElement("li", { id: `fn:${idx}` }, react_1.default.createElement("p", null, def + " ", react_1.default.createElement("a", { href: `#fnref:${idx}`, className: "footnote-backref" }, "↩")));
116
+ });
117
+ return react_1.default.createElement("section", { className: "footnotes" }, react_1.default.createElement("ol", null, ...items));
118
+ }
119
+ else
120
+ return null;
121
+ }
122
+ }
123
+ exports.default = ReactRenderer;
@@ -0,0 +1,15 @@
1
+ declare abstract class Resolver {
2
+ abstract isResolverValid(): boolean;
3
+ }
4
+ export declare class FootnoteResolver extends Resolver {
5
+ private defs;
6
+ private usedRef;
7
+ addDef(id: string, content: string): void;
8
+ addUsedRef(id: string): void;
9
+ resolve(id: string): string | undefined;
10
+ getUsedRef(): string[];
11
+ getUsedRefById(id: string): number;
12
+ getDef(id: string): string | undefined;
13
+ isResolverValid(): boolean;
14
+ }
15
+ export {};
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FootnoteResolver = void 0;
4
+ class Resolver {
5
+ }
6
+ class FootnoteResolver extends Resolver {
7
+ constructor() {
8
+ super(...arguments);
9
+ this.defs = new Map();
10
+ this.usedRef = [];
11
+ }
12
+ addDef(id, content) {
13
+ this.defs.set(id, content);
14
+ }
15
+ addUsedRef(id) {
16
+ if (!this.usedRef.includes(id)) {
17
+ this.usedRef.push(id);
18
+ }
19
+ }
20
+ resolve(id) {
21
+ return this.defs.get(id);
22
+ }
23
+ getUsedRef() {
24
+ return this.usedRef;
25
+ }
26
+ getUsedRefById(id) {
27
+ return this.usedRef.indexOf(id) + 1;
28
+ }
29
+ getDef(id) {
30
+ return this.defs.get(id);
31
+ }
32
+ isResolverValid() {
33
+ return this.defs.size !== 0 && this.usedRef.length !== 0;
34
+ }
35
+ }
36
+ exports.FootnoteResolver = FootnoteResolver;
@@ -21,6 +21,10 @@
21
21
  * - Image: An image, with it's `src` and `alt`
22
22
  * - HorizontalLine: A horizontal line
23
23
  * - Text: Raw text content.
24
+ * - Table: A table, with it's rows
25
+ * - HTMLBlock: A HTML block element, with it's `value`
26
+ * - HTMLInline: An inline HTML element, with it's `value`
27
+ * - FootnoteRef: A refernce with it's `id`
24
28
  */
25
29
  export type Node = {
26
30
  type: "Document";
@@ -79,11 +83,30 @@ export type Node = {
79
83
  } | {
80
84
  type: "Table";
81
85
  rows: TableRow[];
86
+ } | {
87
+ type: "HTMLBlock";
88
+ value: string;
89
+ } | {
90
+ type: "HTMLInline";
91
+ value: string;
92
+ } | {
93
+ type: "FootnoteRef";
94
+ id: string;
82
95
  };
96
+ /**
97
+ * A subtype represent a row of table
98
+ * @property isHeader - If this row is header
99
+ * @property cells: List cells of this row
100
+ */
83
101
  export type TableRow = {
84
102
  isHeader: boolean;
85
103
  cells: TableCell[];
86
104
  };
105
+ /**
106
+ * A subtype represent a table cell
107
+ * @property align - Cell's align
108
+ * @property children - Cell's children nodes
109
+ */
87
110
  export type TableCell = {
88
111
  align: "left" | "center" | "right";
89
112
  children: Node[];
@@ -0,0 +1,11 @@
1
+ /**
2
+ * General option for the render process,
3
+ * These rules define high-level behavior for render process.
4
+ */
5
+ export type ConvertOption = {
6
+ /**
7
+ * Allow raw HTML rendered in the Markdown input.
8
+ * When false (default), HTML tags are escaped for security
9
+ */
10
+ allowDangerousHtml: boolean;
11
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,17 @@
1
+ import { ConvertOption } from "./converterOptions";
2
+ import { ReactRenderOption } from "./reactRenderOptions";
3
+ import { RenderOption } from "./renderOptions";
4
+ /**
5
+ * General option for rendering Markdown into HTML strings
6
+ */
7
+ export type MarkdownDefaultOptions = {
8
+ renderOptions?: RenderOption;
9
+ converterOptions?: ConvertOption;
10
+ };
11
+ /**
12
+ * General option for rendering Markdown into `React.ReactNode` elements
13
+ */
14
+ export type MarkdownReactOptions = {
15
+ renderOptions?: ReactRenderOption;
16
+ converterOptions?: ConvertOption;
17
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,50 @@
1
+ import { Node } from "../node";
2
+ /**
3
+ * Function type for rendering an AST node to a ReactNode.
4
+ *
5
+ * @template T - A subtype of `Node` corresponding to the render node
6
+ * @param node - The AST node to render
7
+ * @param children - An array of rendered `ReactNode` from the node's children
8
+ * @returns A `React.ReactNode` representation of the node
9
+ */
10
+ type ReactNodeRenderer<T extends Node = Node> = (node: T, children: React.ReactNode[]) => React.ReactNode;
11
+ /**
12
+ * A mapping of AST node types to custom render functions.
13
+ *
14
+ * - The key is a `Node["type"]` string literal (e.g. `"Header"`, `"Paragraph"`)
15
+ * - The value is a function `ReactNodeRenderer` function:
16
+ * - `node` is a `Node` with its attribute depending on its `type`.
17
+ * (e.g. `"Header"` nodes include `level`, `"CodeBlock"` nodes include `lang` and `content`, etc)
18
+ * - `children` is the array of rendered `ReactNode` of its children.
19
+ */
20
+ export type ReactRenderElements = {
21
+ [K in Node["type"]]?: ReactNodeRenderer<Extract<Node, {
22
+ type: K;
23
+ }>>;
24
+ };
25
+ /**
26
+ * Options to customize how AST nodes are renderes into ReactNode elements
27
+ *
28
+ * @property elements - Optional custom rendered for one or more node types
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * // Using JSX (Recommended for most users)
33
+ * const renderOptions: ReactRenderOption = {
34
+ * elements: {
35
+ * Paragraph: (_node, children) => <p className="paragraph">{children}</p>,
36
+ * Bold: (_node, children) => <strong className="bold-text">{children}</strong>,
37
+ * }
38
+ * }
39
+ * // Or using React.createElement (Common in library core or without JSX)
40
+ * const renderOptions: ReactRenderOption = {
41
+ * elements: {
42
+ * Bold: (_node, children) => React.createElement("b", { className: "bold" }, ...children),
43
+ * }
44
+ * }
45
+ * ```
46
+ */
47
+ export type ReactRenderOption = {
48
+ elements?: ReactRenderElements;
49
+ };
50
+ export {};
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,44 @@
1
+ import { Node } from "../node";
2
+ /**
3
+ * Function type for rendering an AST node to HTML.
4
+ *
5
+ * @template T - A subtype of `Node` corresponding to the render node
6
+ * @param node - The AST node to render
7
+ * @param children - Rendered HTML strings of the node's children
8
+ * @returns A HTML string representation of the node
9
+ */
10
+ type NodeRenderer<T extends Node = Node> = (node: T, children: string[]) => string;
11
+ /**
12
+ * A mapping of AST node types to custom render functions.
13
+ *
14
+ * - The key is a `Node["type"]` string literal (e.g. `"Header"`, `"Paragraph"`)
15
+ * - The value is a function `(node, children) => string`:
16
+ * - `node` is a `Node` with its attribute depending on its `type`.
17
+ * (e.g. `"Header"` nodes include `level`, `"CodeBlock"` nodes include `lang` and `content`, etc)
18
+ * - `children` is the array of rendered strings of its children.
19
+ */
20
+ export type RenderElements = {
21
+ [K in Node["type"]]?: NodeRenderer<Extract<Node, {
22
+ type: K;
23
+ }>>;
24
+ };
25
+ /**
26
+ * Options to customize how AST nodes are renderes into HTML
27
+ *
28
+ * @property elements - Optional custom rendered for one or more node types
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * const renderOptions: RenderOption = {
33
+ * elements: {
34
+ * Paragraph: (_node, children) => `<div class="paragraph">${children.join("")}</div>`,
35
+ * Bold: (_node, children) => `<b class="bold-text">${children.join("")}</b>`,
36
+ * }
37
+ * }
38
+ * ```
39
+ *
40
+ */
41
+ export type RenderOption = {
42
+ elements?: RenderElements;
43
+ };
44
+ export {};
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -21,6 +21,16 @@
21
21
  * - Image: An image (`![alt](url)`)
22
22
  * - HorizontalLine: A horizontal line (`---` or `___` or `***`)
23
23
  * - Text: Plain text content.
24
+ * - TableStart: Start of a table
25
+ * - TableEnd: End of a table
26
+ * - RowStart: Start of a table row
27
+ * - RowEnd: End of a table row
28
+ * - CellStart: Start of a table cell, with it's align accroding to it's row
29
+ * - CellEnd: End of a table cell
30
+ * - HTMLBlock: A HTML block element
31
+ * - HTMLInline: An inline HTML element
32
+ * - FootnodeDef: Definition of a footnote
33
+ * - FootnodeRef: The reference of a footnote
24
34
  * - EOF: A special token, this is the end of input.
25
35
  */
26
36
  export type Token = {
@@ -67,8 +77,6 @@ export type Token = {
67
77
  } | {
68
78
  type: "Text";
69
79
  value: string;
70
- } | {
71
- type: "EOF";
72
80
  } | {
73
81
  type: "TableStart";
74
82
  } | {
@@ -83,4 +91,19 @@ export type Token = {
83
91
  align: "left" | "center" | "right";
84
92
  } | {
85
93
  type: "CellEnd";
94
+ } | {
95
+ type: "HTMLBlock";
96
+ value: string;
97
+ } | {
98
+ type: "HTMLInline";
99
+ value: string;
100
+ } | {
101
+ type: "FootnoteDef";
102
+ id: string;
103
+ content: string;
104
+ } | {
105
+ type: "FootnoteRef";
106
+ id: string;
107
+ } | {
108
+ type: "EOF";
86
109
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simple-customize-markdown-converter",
3
- "version": "1.0.6",
3
+ "version": "1.1.0",
4
4
  "description": "Convert Markdown to your customize HTML",
5
5
  "keywords": [
6
6
  "markdown",
@@ -17,15 +17,34 @@
17
17
  "LICENSE",
18
18
  "README.md"
19
19
  ],
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ },
25
+ "./react": {
26
+ "types": "./dist/react.d.ts",
27
+ "default": "./dist/react.js"
28
+ }
29
+ },
20
30
  "scripts": {
21
31
  "test": "jest",
22
32
  "build": "tsc",
23
33
  "start": "ts-node src/index.ts"
24
34
  },
35
+ "peerDependencies": {
36
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.2.0",
37
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.2.0"
38
+ },
25
39
  "devDependencies": {
26
40
  "@types/jest": "^30.0.0",
27
41
  "@types/node": "^24.3.3",
42
+ "@types/react": "^19.2.7",
43
+ "@types/react-dom": "^19.2.3",
28
44
  "jest": "^30.1.3",
45
+ "jest-environment-jsdom": "^30.2.0",
46
+ "react": "^19.2.3",
47
+ "react-dom": "^19.2.3",
29
48
  "ts-jest": "^29.4.1",
30
49
  "ts-node": "^10.9.2",
31
50
  "typescript": "^5.9.2"