simple-customize-markdown-converter 1.0.7 → 1.2.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.
- package/README.md +74 -17
- package/dist/core/lexer/handler.d.ts +23 -0
- package/dist/core/lexer/handler.js +272 -0
- package/dist/core/lexer/index.d.ts +42 -0
- package/dist/core/lexer/index.js +177 -0
- package/dist/core/lexer.d.ts +46 -0
- package/dist/core/lexer.js +433 -0
- package/dist/core/parser/handler.d.ts +19 -0
- package/dist/core/parser/handler.js +254 -0
- package/dist/core/parser/index.d.ts +33 -0
- package/dist/core/parser/index.js +149 -0
- package/dist/core/parser.d.ts +37 -0
- package/dist/core/parser.js +346 -0
- package/dist/core/renderer.d.ts +3 -0
- package/dist/core/renderer.js +99 -0
- package/dist/core/resolver/footnote-resolver.d.ts +15 -0
- package/dist/core/resolver/footnote-resolver.js +36 -0
- package/dist/core/resolver.d.ts +15 -0
- package/dist/core/resolver.js +36 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.js +10 -7
- package/dist/react.d.ts +36 -0
- package/dist/react.js +56 -0
- package/dist/renderers/default/handler.d.ts +21 -0
- package/dist/renderers/default/handler.js +114 -0
- package/dist/renderers/default/index.d.ts +14 -0
- package/dist/renderers/default/index.js +117 -0
- package/dist/renderers/default.d.ts +26 -0
- package/dist/renderers/default.js +105 -0
- package/dist/renderers/index.d.ts +10 -0
- package/dist/renderers/index.js +2 -0
- package/dist/renderers/react/handler.d.ts +22 -0
- package/dist/renderers/react/handler.js +123 -0
- package/dist/renderers/react/index.d.ts +15 -0
- package/dist/renderers/react/index.js +123 -0
- package/dist/renderers/react.d.ts +26 -0
- package/dist/renderers/react.js +123 -0
- package/dist/types/options/converterOptions.d.ts +11 -0
- package/dist/types/options/converterOptions.js +2 -0
- package/dist/types/options/index.d.ts +10 -0
- package/dist/types/options/index.js +2 -0
- package/dist/types/options/reactRenderOptions.d.ts +50 -0
- package/dist/types/options/reactRenderOptions.js +2 -0
- package/dist/types/options/renderOptions.d.ts +86 -0
- package/dist/types/options/renderOptions.js +2 -0
- package/dist/types/parser.d.ts +132 -0
- package/dist/types/parser.js +2 -0
- package/dist/types/renderer.d.ts +12 -0
- package/dist/types/renderer.js +2 -0
- package/dist/types/token.d.ts +94 -74
- package/dist/utilities/parser-utils.d.ts +5 -0
- package/dist/utilities/parser-utils.js +65 -0
- package/dist/utilities/tokenizer-utils.d.ts +11 -0
- package/dist/utilities/tokenizer-utils.js +159 -0
- package/package.json +24 -3
package/dist/react.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
exports.MarkdownComponent = void 0;
|
|
7
|
+
exports.convertMarkdownToReactNode = convertMarkdownToReactNode;
|
|
8
|
+
const react_1 = __importDefault(require("react"));
|
|
9
|
+
const lexer_1 = __importDefault(require("./core/lexer"));
|
|
10
|
+
const parser_1 = require("./core/parser");
|
|
11
|
+
const footnote_resolver_1 = require("./core/resolver/footnote-resolver");
|
|
12
|
+
const react_2 = require("./renderers/react");
|
|
13
|
+
/**
|
|
14
|
+
* Convert a Markdown string into a ReactNode.
|
|
15
|
+
* @param input - The Markdown source string
|
|
16
|
+
* @param renderOptions - Optional rendering options
|
|
17
|
+
* @param options - Optional handle options
|
|
18
|
+
* @returns The rendered `React.ReactNode` ready to be rendered into a React component.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* const node = convertMarkdownToReactNode("## Hello React");
|
|
23
|
+
* return <div>{node}</div>;
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
function convertMarkdownToReactNode(input, options = {
|
|
27
|
+
renderOptions: {},
|
|
28
|
+
converterOptions: { allowDangerousHtml: false }
|
|
29
|
+
}) {
|
|
30
|
+
const tokens = new lexer_1.default(input).tokenize();
|
|
31
|
+
const footNoteResolver = new footnote_resolver_1.FootnoteResolver();
|
|
32
|
+
const nodes = new parser_1.Parser(tokens, footNoteResolver).parse();
|
|
33
|
+
return new react_2.ReactRenderer(footNoteResolver, options).render(nodes);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A React commponent that renders Markdown content.
|
|
37
|
+
* Using `React.useMemo` to ensure performance and prevent unnecessary re-render.
|
|
38
|
+
* @param props.content - The Markdown source to render.
|
|
39
|
+
* @param props.options - Optional configuration for the renderer.
|
|
40
|
+
* @param props.className - Optional CSS classes for the wrapping `div` element.
|
|
41
|
+
* @example
|
|
42
|
+
* ```tsx
|
|
43
|
+
* <MarkdownComponent
|
|
44
|
+
* content="**Bold text here**"
|
|
45
|
+
* className="markdown markdown-rendered"
|
|
46
|
+
* options={{ converterOptions: { allowDangerousHtml: true } }}
|
|
47
|
+
* />
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
const MarkdownComponent = ({ content, className, options }) => {
|
|
51
|
+
const rendered = react_1.default.useMemo(() => {
|
|
52
|
+
return convertMarkdownToReactNode(content, options);
|
|
53
|
+
}, [content]);
|
|
54
|
+
return react_1.default.createElement("div", { className }, rendered);
|
|
55
|
+
};
|
|
56
|
+
exports.MarkdownComponent = MarkdownComponent;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { RenderStrategy } from "../../types/renderer";
|
|
2
|
+
export declare const DocumentHandler: RenderStrategy<string>;
|
|
3
|
+
export declare const ParagraphHandler: RenderStrategy<string>;
|
|
4
|
+
export declare const CodeBlockHandler: RenderStrategy<string>;
|
|
5
|
+
export declare const HeaderHandler: RenderStrategy<string>;
|
|
6
|
+
export declare const QuoteHandler: RenderStrategy<string>;
|
|
7
|
+
export declare const ListHandler: RenderStrategy<string>;
|
|
8
|
+
export declare const ListItemHandler: RenderStrategy<string>;
|
|
9
|
+
export declare const TaskItemHandler: RenderStrategy<string>;
|
|
10
|
+
export declare const BoldHandler: RenderStrategy<string>;
|
|
11
|
+
export declare const ItalicHandler: RenderStrategy<string>;
|
|
12
|
+
export declare const StrikethroughHandler: RenderStrategy<string>;
|
|
13
|
+
export declare const InlineCodeHandler: RenderStrategy<string>;
|
|
14
|
+
export declare const LinkHandler: RenderStrategy<string>;
|
|
15
|
+
export declare const ImageHandler: RenderStrategy<string>;
|
|
16
|
+
export declare const HorizontalLineHandler: RenderStrategy<string>;
|
|
17
|
+
export declare const TextHandler: RenderStrategy<string>;
|
|
18
|
+
export declare const TableHandler: RenderStrategy<string>;
|
|
19
|
+
export declare const HTMLBlockHandler: RenderStrategy<string>;
|
|
20
|
+
export declare const HTMLInlineHandler: RenderStrategy<string>;
|
|
21
|
+
export declare const FootnoteRefHandler: RenderStrategy<string>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FootnoteRefHandler = exports.HTMLInlineHandler = exports.HTMLBlockHandler = exports.TableHandler = exports.TextHandler = exports.HorizontalLineHandler = exports.ImageHandler = exports.LinkHandler = exports.InlineCodeHandler = exports.StrikethroughHandler = exports.ItalicHandler = exports.BoldHandler = exports.TaskItemHandler = exports.ListItemHandler = exports.ListHandler = exports.QuoteHandler = exports.HeaderHandler = exports.CodeBlockHandler = exports.ParagraphHandler = exports.DocumentHandler = void 0;
|
|
4
|
+
//Base structural nodes
|
|
5
|
+
exports.DocumentHandler = {
|
|
6
|
+
type: "Document",
|
|
7
|
+
render: (_node, children, ctx) => children.join("") + ctx.renderFootnotes()
|
|
8
|
+
};
|
|
9
|
+
exports.ParagraphHandler = {
|
|
10
|
+
type: "Paragraph",
|
|
11
|
+
render: (_node, children) => `<p>${children.join("")}</p>`
|
|
12
|
+
};
|
|
13
|
+
//Container nodes
|
|
14
|
+
exports.CodeBlockHandler = {
|
|
15
|
+
type: "CodeBlock",
|
|
16
|
+
render: (node) => `<pre><code class="lang-${node.lang}">${escapeHtml(node.content || "")}</code></pre>`,
|
|
17
|
+
};
|
|
18
|
+
exports.HeaderHandler = {
|
|
19
|
+
type: "Header",
|
|
20
|
+
render: (node, children) => {
|
|
21
|
+
if (node.level) {
|
|
22
|
+
const style = node.level <= 2 ? ' style="border-bottom: 1px solid #d1d9e0b3"' : '';
|
|
23
|
+
return `<h${node.level}${style}>${children.join("")}</h${node.level}>`;
|
|
24
|
+
}
|
|
25
|
+
return `<p>${children.join("")}</p>`;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
exports.QuoteHandler = {
|
|
29
|
+
type: "Quote",
|
|
30
|
+
render: (_node, children) => `<blockquote style="margin:0; padding:0 1em; color:#59636e; border-left:.25em solid #d1d9e0;">${children.join("")}</blockquote>`
|
|
31
|
+
};
|
|
32
|
+
//For list nodes
|
|
33
|
+
exports.ListHandler = {
|
|
34
|
+
type: "List",
|
|
35
|
+
render: (node, children) => node.ordered ? `<ol>${children.join("")}</ol>` : `<ul>${children.join("")}</ul>`
|
|
36
|
+
};
|
|
37
|
+
exports.ListItemHandler = {
|
|
38
|
+
type: "ListItem",
|
|
39
|
+
render: (_node, children) => `<li>${children.join("")}</li>`
|
|
40
|
+
};
|
|
41
|
+
exports.TaskItemHandler = {
|
|
42
|
+
type: "TaskItem",
|
|
43
|
+
render: (node, children) => `<li><input type="checkbox" disabled ${node.checked ? "checked" : ""}>${children.join("")}</li>`
|
|
44
|
+
};
|
|
45
|
+
//Styling nodes
|
|
46
|
+
exports.BoldHandler = {
|
|
47
|
+
type: "Bold",
|
|
48
|
+
render: (_node, children) => `<strong>${children.join("")}</strong>`
|
|
49
|
+
};
|
|
50
|
+
exports.ItalicHandler = {
|
|
51
|
+
type: "Italic",
|
|
52
|
+
render: (_node, children) => `<em>${children.join("")}</em>`
|
|
53
|
+
};
|
|
54
|
+
exports.StrikethroughHandler = {
|
|
55
|
+
type: "Strikethrough",
|
|
56
|
+
render: (_node, children) => `<s>${children.join("")}</s>`
|
|
57
|
+
};
|
|
58
|
+
exports.InlineCodeHandler = {
|
|
59
|
+
type: "InlineCode",
|
|
60
|
+
render: (node, _children) => `<code>${escapeHtml(node.content || "")}</code>`
|
|
61
|
+
};
|
|
62
|
+
//Media nodes
|
|
63
|
+
exports.LinkHandler = {
|
|
64
|
+
type: "Link",
|
|
65
|
+
render: (node) => `<a href="${node.href}">${node.text}</a>`
|
|
66
|
+
};
|
|
67
|
+
exports.ImageHandler = {
|
|
68
|
+
type: "Image",
|
|
69
|
+
render: (node) => `<img src="${node.src || ""}" alt="${node.alt || ""}"/>`
|
|
70
|
+
};
|
|
71
|
+
//Leaf nodes
|
|
72
|
+
exports.HorizontalLineHandler = {
|
|
73
|
+
type: "HorizontalLine",
|
|
74
|
+
render: () => `<hr>`
|
|
75
|
+
};
|
|
76
|
+
exports.TextHandler = {
|
|
77
|
+
type: "Text",
|
|
78
|
+
render: (node) => node.value || ""
|
|
79
|
+
};
|
|
80
|
+
//Table nodes
|
|
81
|
+
exports.TableHandler = {
|
|
82
|
+
type: "Table",
|
|
83
|
+
render: (node, children, ctx) => ctx.renderTable(node, children)
|
|
84
|
+
};
|
|
85
|
+
//For HTML
|
|
86
|
+
exports.HTMLBlockHandler = {
|
|
87
|
+
type: "HTMLBlock",
|
|
88
|
+
render: (node, _children, ctx) => {
|
|
89
|
+
const val = node.value || "";
|
|
90
|
+
return ctx.options.converterOptions?.allowDangerousHtml ? val : escapeHtml(val);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
exports.HTMLInlineHandler = {
|
|
94
|
+
type: "HTMLInline",
|
|
95
|
+
render: (node, _children, ctx) => {
|
|
96
|
+
const val = node.value || "";
|
|
97
|
+
return ctx.options.converterOptions?.allowDangerousHtml ? val : escapeHtml(val);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
//For footnote
|
|
101
|
+
exports.FootnoteRefHandler = {
|
|
102
|
+
type: "FootnoteRef",
|
|
103
|
+
render: (node, _children, ctx) => {
|
|
104
|
+
if (node.id) {
|
|
105
|
+
const idx = ctx.footnoteResolver.getUsedRefById(node.id);
|
|
106
|
+
return `<sup id="fnref:${idx}"><a href="#fn:${idx}" class="footnote-ref">[${idx}]</a></sup>`;
|
|
107
|
+
}
|
|
108
|
+
return "";
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
//Utilities
|
|
112
|
+
function escapeHtml(str) {
|
|
113
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
114
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { IRenderer } from "..";
|
|
2
|
+
import { MarkdownOptions } from "../../types/options";
|
|
3
|
+
import { ASTNode } from "../../types/parser";
|
|
4
|
+
import { FootnoteResolver } from "../../core/resolver/footnote-resolver";
|
|
5
|
+
export declare class DefaultRenderer implements IRenderer<string> {
|
|
6
|
+
options: MarkdownOptions<string>;
|
|
7
|
+
footnoteResolver: FootnoteResolver;
|
|
8
|
+
private strategies;
|
|
9
|
+
constructor(footnoteResolver: FootnoteResolver, options?: MarkdownOptions<string>);
|
|
10
|
+
private registerDefaultStrategies;
|
|
11
|
+
render(node: ASTNode): string;
|
|
12
|
+
renderFootnotes(): string;
|
|
13
|
+
renderTable(node: ASTNode, children: string[]): string;
|
|
14
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DefaultRenderer = void 0;
|
|
37
|
+
const Handlers = __importStar(require("./handler"));
|
|
38
|
+
class DefaultRenderer {
|
|
39
|
+
constructor(footnoteResolver, options = {}) {
|
|
40
|
+
this.strategies = new Map();
|
|
41
|
+
this.footnoteResolver = footnoteResolver;
|
|
42
|
+
this.options = options;
|
|
43
|
+
this.registerDefaultStrategies();
|
|
44
|
+
}
|
|
45
|
+
registerDefaultStrategies() {
|
|
46
|
+
const listDefaultStrategy = [
|
|
47
|
+
Handlers.DocumentHandler,
|
|
48
|
+
Handlers.ParagraphHandler,
|
|
49
|
+
Handlers.HeaderHandler,
|
|
50
|
+
Handlers.CodeBlockHandler,
|
|
51
|
+
Handlers.QuoteHandler,
|
|
52
|
+
Handlers.BoldHandler,
|
|
53
|
+
Handlers.ItalicHandler,
|
|
54
|
+
Handlers.StrikethroughHandler,
|
|
55
|
+
Handlers.InlineCodeHandler,
|
|
56
|
+
Handlers.LinkHandler,
|
|
57
|
+
Handlers.ImageHandler,
|
|
58
|
+
Handlers.ListHandler,
|
|
59
|
+
Handlers.ListItemHandler,
|
|
60
|
+
Handlers.TaskItemHandler,
|
|
61
|
+
Handlers.TableHandler,
|
|
62
|
+
Handlers.TextHandler,
|
|
63
|
+
Handlers.HorizontalLineHandler,
|
|
64
|
+
Handlers.HTMLBlockHandler,
|
|
65
|
+
Handlers.HTMLInlineHandler,
|
|
66
|
+
Handlers.FootnoteRefHandler
|
|
67
|
+
];
|
|
68
|
+
listDefaultStrategy.forEach(s => this.strategies.set(s.type, s));
|
|
69
|
+
}
|
|
70
|
+
render(node) {
|
|
71
|
+
const userRenderer = this.options.renderOptions?.elements?.[node.type];
|
|
72
|
+
const children = (node.children || []).map(child => this.render(child));
|
|
73
|
+
if (userRenderer) {
|
|
74
|
+
return userRenderer(node, children);
|
|
75
|
+
}
|
|
76
|
+
const strategy = this.strategies.get(node.type);
|
|
77
|
+
if (strategy) {
|
|
78
|
+
return strategy.render(node, children, this);
|
|
79
|
+
}
|
|
80
|
+
return children.join("");
|
|
81
|
+
}
|
|
82
|
+
renderFootnotes() {
|
|
83
|
+
if (this.footnoteResolver.isResolverValid()) {
|
|
84
|
+
const used = this.footnoteResolver.getUsedRef();
|
|
85
|
+
if (used.length === 0)
|
|
86
|
+
return "";
|
|
87
|
+
const items = used.map((id, i) => {
|
|
88
|
+
const def = this.footnoteResolver.getDef(id) ?? "";
|
|
89
|
+
const idx = i + 1;
|
|
90
|
+
return `<li id="fn:${idx}"><p>${def} <a href="#fnref:${idx}" class="footnote-backref">↩</a></p></li>`;
|
|
91
|
+
});
|
|
92
|
+
return `<section class="footnotes"><ol>${items.join("")}</ol></section>`;
|
|
93
|
+
}
|
|
94
|
+
else
|
|
95
|
+
return "";
|
|
96
|
+
}
|
|
97
|
+
renderTable(node, children) {
|
|
98
|
+
if (node.type === "Table" && node.rows) {
|
|
99
|
+
const header = node.rows.filter(row => row.isHeader);
|
|
100
|
+
const body = node.rows.filter(row => !row.isHeader);
|
|
101
|
+
const renderRows = (row) => {
|
|
102
|
+
const tag = row.isHeader ? "th" : "td";
|
|
103
|
+
const cells = row.cells.map(cell => {
|
|
104
|
+
const align = `style="text-align:${cell.align}"`;
|
|
105
|
+
return `<${tag} ${align}>${cell.children.map(c => this.render(c)).join("")}</${tag}>`;
|
|
106
|
+
}).join("");
|
|
107
|
+
return `<tr>${cells}</tr>`;
|
|
108
|
+
};
|
|
109
|
+
const tHead = header.length ? `<thead>${header.map(renderRows).join("")}</thead>` : "";
|
|
110
|
+
const tBody = body.length ? `<tbody>${body.map(renderRows).join("")}</tbody>` : "";
|
|
111
|
+
return `<table>${tHead}${tBody}</table>`;
|
|
112
|
+
}
|
|
113
|
+
else
|
|
114
|
+
return `<p>${children.join("\n")}</p>`;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
exports.DefaultRenderer = DefaultRenderer;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { FootnoteResolver } from "../core/resolver";
|
|
2
|
+
import { Node } from "../types/node";
|
|
3
|
+
import { MarkdownDefaultOptions } from "../types/options";
|
|
4
|
+
export default class DefaultRenderer {
|
|
5
|
+
options: MarkdownDefaultOptions;
|
|
6
|
+
footNoteResolver: FootnoteResolver;
|
|
7
|
+
constructor(options: MarkdownDefaultOptions, footNoteResolver: FootnoteResolver);
|
|
8
|
+
/**
|
|
9
|
+
* Render a Node (AST) to a HTML string according renderer options
|
|
10
|
+
*
|
|
11
|
+
* @param node - The abstract syntax tree (AST) from the Parser
|
|
12
|
+
* @returns The rendered HTML string.
|
|
13
|
+
*/
|
|
14
|
+
render<K extends Node["type"]>(node: Extract<Node, {
|
|
15
|
+
type: K;
|
|
16
|
+
}>): string;
|
|
17
|
+
/**
|
|
18
|
+
* Select the appropriate rendering handler for a specific node type
|
|
19
|
+
* @param type - The type of AST Note
|
|
20
|
+
* @returns A function take a node and its children to procude a string.
|
|
21
|
+
*/
|
|
22
|
+
private handleRender;
|
|
23
|
+
private renderTable;
|
|
24
|
+
private escapeHtml;
|
|
25
|
+
private renderFootnotes;
|
|
26
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
class DefaultRenderer {
|
|
4
|
+
constructor(options, footNoteResolver) {
|
|
5
|
+
this.options = options;
|
|
6
|
+
this.footNoteResolver = footNoteResolver;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Render a Node (AST) to a HTML string according renderer options
|
|
10
|
+
*
|
|
11
|
+
* @param node - The abstract syntax tree (AST) from the Parser
|
|
12
|
+
* @returns The rendered HTML string.
|
|
13
|
+
*/
|
|
14
|
+
render(node) {
|
|
15
|
+
//Get proper handler type
|
|
16
|
+
const handler = this.handleRender(node.type);
|
|
17
|
+
//If node have children, recursive to handle all node's children
|
|
18
|
+
const children = "children" in node ? node.children.map((ele) => this.render(ele)) : [];
|
|
19
|
+
return handler(node, children);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Select the appropriate rendering handler for a specific node type
|
|
23
|
+
* @param type - The type of AST Note
|
|
24
|
+
* @returns A function take a node and its children to procude a string.
|
|
25
|
+
*/
|
|
26
|
+
handleRender(type) {
|
|
27
|
+
const defaultRender = {
|
|
28
|
+
//Base structural nodes
|
|
29
|
+
Document: (_node, children) => children.join("") + this.renderFootnotes(),
|
|
30
|
+
Paragraph: (_node, children) => `<p>${children.join("")}</p>`,
|
|
31
|
+
//Container nodes
|
|
32
|
+
CodeBlock: (node) => `<pre><code class="lang-${node.lang}">${this.escapeHtml(node.content)}</code></pre>`,
|
|
33
|
+
Header: (node, children) => `<h${node.level}${node.level <= 2 ? ' style="border-bottom: 1px solid #d1d9e0b3"' : ''}>${children.join("")}</h${node.level}>`,
|
|
34
|
+
Quote: (_node, children) => `<blockquote style="margin:0; padding:0 1em; color:#59636e; border-left:.25em solid #d1d9e0;">${children.join("")}</blockquote>`,
|
|
35
|
+
//For list nodes
|
|
36
|
+
List: (node, children) => node.ordered ? `<ol>${children.join("")}</ol>` : `<ul>${children.join("")}</ul>`,
|
|
37
|
+
ListItem: (_node, children) => `<li>${children.join("")}</li>`,
|
|
38
|
+
TaskItem: (node, children) => `<li><input type="checkbox" disabled ${node.checked ? "checked" : ""}>${children.join("")}</li>`,
|
|
39
|
+
//Styling nodes
|
|
40
|
+
Bold: (_node, children) => `<strong>${children.join("")}</strong>`,
|
|
41
|
+
Italic: (_node, children) => `<em>${children.join("")}</em>`,
|
|
42
|
+
Strikethrough: (_node, children) => `<s>${children.join("")}</s>`,
|
|
43
|
+
InlineCode: (node) => `<code>${this.escapeHtml(node.content)}</code>`,
|
|
44
|
+
//Media nodes
|
|
45
|
+
Link: (node) => `<a href="${node.href}">${node.text}</a>`,
|
|
46
|
+
Image: (node) => `<img src="${node.src}" alt="${node.alt}"/>`,
|
|
47
|
+
//Leaf nodes
|
|
48
|
+
HorizontalLine: (_node) => `<hr>`,
|
|
49
|
+
Text: (node) => node.value,
|
|
50
|
+
//For table nodes
|
|
51
|
+
Table: (node, children) => this.renderTable(node, children),
|
|
52
|
+
//For HTML
|
|
53
|
+
HTMLBlock: (node) => this.options.converterOptions?.allowDangerousHtml
|
|
54
|
+
? node.value
|
|
55
|
+
: this.escapeHtml(node.value),
|
|
56
|
+
HTMLInline: (node) => this.options.converterOptions?.allowDangerousHtml
|
|
57
|
+
? node.value
|
|
58
|
+
: this.escapeHtml(node.value),
|
|
59
|
+
//For footnote
|
|
60
|
+
FootnoteRef: (node) => {
|
|
61
|
+
const idx = this.footNoteResolver.getUsedRefById(node.id);
|
|
62
|
+
return `<sup id="fnref:${idx}"><a href="#fn:${idx}" class="footnote-ref">[${idx}]</a></sup>`;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
return (this.options.renderOptions?.elements?.[type] ?? defaultRender[type]);
|
|
66
|
+
}
|
|
67
|
+
renderTable(node, children) {
|
|
68
|
+
if (node.type === "Table") {
|
|
69
|
+
const header = node.rows.filter(row => row.isHeader);
|
|
70
|
+
const body = node.rows.filter(row => !row.isHeader);
|
|
71
|
+
const renderRows = (row) => {
|
|
72
|
+
const tag = row.isHeader ? "th" : "td";
|
|
73
|
+
const cells = row.cells.map(cell => {
|
|
74
|
+
const align = `style="text-align:${cell.align}"`;
|
|
75
|
+
return `<${tag} ${align}>${cell.children.map(c => this.render(c)).join("")}</${tag}>`;
|
|
76
|
+
}).join("");
|
|
77
|
+
return `<tr>${cells}</tr>`;
|
|
78
|
+
};
|
|
79
|
+
const tHead = header.length ? `<thead>${header.map(renderRows).join("")}</thead>` : "";
|
|
80
|
+
const tBody = body.length ? `<tbody>${body.map(renderRows).join("")}</tbody>` : "";
|
|
81
|
+
return `<table>${tHead}${tBody}</table>`;
|
|
82
|
+
}
|
|
83
|
+
else
|
|
84
|
+
return `<p>${children.join("\n")}</p>`;
|
|
85
|
+
}
|
|
86
|
+
escapeHtml(str) {
|
|
87
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
88
|
+
}
|
|
89
|
+
renderFootnotes() {
|
|
90
|
+
if (this.footNoteResolver.isResolverValid()) {
|
|
91
|
+
const used = this.footNoteResolver.getUsedRef();
|
|
92
|
+
if (used.length === 0)
|
|
93
|
+
return "";
|
|
94
|
+
const items = used.map((id, i) => {
|
|
95
|
+
const def = this.footNoteResolver.getDef(id) ?? "";
|
|
96
|
+
const idx = i + 1;
|
|
97
|
+
return `<li id="fn:${idx}"><p>${def} <a href="#fnref:${idx}" class="footnote-backref">↩</a></p></li>`;
|
|
98
|
+
});
|
|
99
|
+
return `<section class="footnotes"><ol>${items.join("")}</ol></section>`;
|
|
100
|
+
}
|
|
101
|
+
else
|
|
102
|
+
return "";
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
exports.default = DefaultRenderer;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ASTNode } from "../types/parser";
|
|
2
|
+
import { MarkdownOptions } from '../types/options/index';
|
|
3
|
+
import { FootnoteResolver } from "../core/resolver/footnote-resolver";
|
|
4
|
+
export interface IRenderer<TOutput> {
|
|
5
|
+
options: MarkdownOptions<TOutput>;
|
|
6
|
+
footnoteResolver: FootnoteResolver;
|
|
7
|
+
render(node: ASTNode): TOutput;
|
|
8
|
+
renderTable(node: ASTNode, children: TOutput[]): TOutput;
|
|
9
|
+
renderFootnotes(): TOutput;
|
|
10
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { RenderStrategy } from "../../types/renderer";
|
|
3
|
+
export declare const DocumentHandler: RenderStrategy<ReactNode>;
|
|
4
|
+
export declare const ParagraphHandler: RenderStrategy<ReactNode>;
|
|
5
|
+
export declare const CodeBlockHandler: RenderStrategy<ReactNode>;
|
|
6
|
+
export declare const HeaderHandler: RenderStrategy<ReactNode>;
|
|
7
|
+
export declare const QuoteHandler: RenderStrategy<ReactNode>;
|
|
8
|
+
export declare const ListHandler: RenderStrategy<ReactNode>;
|
|
9
|
+
export declare const ListItemHandler: RenderStrategy<ReactNode>;
|
|
10
|
+
export declare const TaskItemHandler: RenderStrategy<ReactNode>;
|
|
11
|
+
export declare const BoldHandler: RenderStrategy<ReactNode>;
|
|
12
|
+
export declare const ItalicHandler: RenderStrategy<ReactNode>;
|
|
13
|
+
export declare const StrikethroughHandler: RenderStrategy<ReactNode>;
|
|
14
|
+
export declare const InlineCodeHandler: RenderStrategy<ReactNode>;
|
|
15
|
+
export declare const LinkHandler: RenderStrategy<ReactNode>;
|
|
16
|
+
export declare const ImageHandler: RenderStrategy<ReactNode>;
|
|
17
|
+
export declare const HorizontalLineHandler: RenderStrategy<ReactNode>;
|
|
18
|
+
export declare const TextHandler: RenderStrategy<ReactNode>;
|
|
19
|
+
export declare const TableHandler: RenderStrategy<ReactNode>;
|
|
20
|
+
export declare const HTMLBlockHandler: RenderStrategy<ReactNode>;
|
|
21
|
+
export declare const HTMLInlineHandler: RenderStrategy<ReactNode>;
|
|
22
|
+
export declare const FootnoteRefHandler: RenderStrategy<ReactNode>;
|
|
@@ -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
|
+
exports.FootnoteRefHandler = exports.HTMLInlineHandler = exports.HTMLBlockHandler = exports.TableHandler = exports.TextHandler = exports.HorizontalLineHandler = exports.ImageHandler = exports.LinkHandler = exports.InlineCodeHandler = exports.StrikethroughHandler = exports.ItalicHandler = exports.BoldHandler = exports.TaskItemHandler = exports.ListItemHandler = exports.ListHandler = exports.QuoteHandler = exports.HeaderHandler = exports.CodeBlockHandler = exports.ParagraphHandler = exports.DocumentHandler = void 0;
|
|
7
|
+
const react_1 = __importDefault(require("react"));
|
|
8
|
+
//Base structural nodes
|
|
9
|
+
exports.DocumentHandler = {
|
|
10
|
+
type: "Document",
|
|
11
|
+
render: (_node, children, ctx) => react_1.default.createElement(react_1.default.Fragment, null, ...children, ctx.renderFootnotes())
|
|
12
|
+
};
|
|
13
|
+
exports.ParagraphHandler = {
|
|
14
|
+
type: "Paragraph",
|
|
15
|
+
render: (_node, children) => react_1.default.createElement("p", null, ...children)
|
|
16
|
+
};
|
|
17
|
+
//Container nodes
|
|
18
|
+
exports.CodeBlockHandler = {
|
|
19
|
+
type: "CodeBlock",
|
|
20
|
+
render: (node) => react_1.default.createElement("pre", null, react_1.default.createElement("code", { className: `lang-${node.lang}` }, node.content || ""))
|
|
21
|
+
};
|
|
22
|
+
exports.HeaderHandler = {
|
|
23
|
+
type: "Header",
|
|
24
|
+
render: (node, children) => {
|
|
25
|
+
if (!node.level)
|
|
26
|
+
return react_1.default.createElement("p", null, ...children);
|
|
27
|
+
return react_1.default.createElement(`h${node.level}`, { style: { borderBottom: node.level <= 2 ? "1px solid #d1d9e0b3" : undefined } }, ...children);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
exports.QuoteHandler = {
|
|
31
|
+
type: "Quote",
|
|
32
|
+
render: (_node, children) => react_1.default.createElement("blockquote", { style: { margin: "0", padding: "0 1em", color: "#59636e", borderLeft: ".25em solid #d1d9e0" } }, ...children)
|
|
33
|
+
};
|
|
34
|
+
//For list nodes
|
|
35
|
+
exports.ListHandler = {
|
|
36
|
+
type: "List",
|
|
37
|
+
render: (node, children) => react_1.default.createElement(node.ordered ? "ol" : "ul", null, ...children)
|
|
38
|
+
};
|
|
39
|
+
exports.ListItemHandler = {
|
|
40
|
+
type: "ListItem",
|
|
41
|
+
render: (_node, children) => react_1.default.createElement("li", null, ...children)
|
|
42
|
+
};
|
|
43
|
+
exports.TaskItemHandler = {
|
|
44
|
+
type: "TaskItem",
|
|
45
|
+
render: (node, children) => react_1.default.createElement("li", { style: { listStyleType: "none" } }, react_1.default.createElement("input", {
|
|
46
|
+
type: "checkbox",
|
|
47
|
+
disabled: true,
|
|
48
|
+
checked: !!node.checked,
|
|
49
|
+
readOnly: true
|
|
50
|
+
}), ...children)
|
|
51
|
+
};
|
|
52
|
+
//Styling nodes
|
|
53
|
+
exports.BoldHandler = {
|
|
54
|
+
type: "Bold",
|
|
55
|
+
render: (_node, children) => react_1.default.createElement("strong", null, ...children)
|
|
56
|
+
};
|
|
57
|
+
exports.ItalicHandler = {
|
|
58
|
+
type: "Italic",
|
|
59
|
+
render: (_node, children) => react_1.default.createElement("em", null, ...children)
|
|
60
|
+
};
|
|
61
|
+
exports.StrikethroughHandler = {
|
|
62
|
+
type: "Strikethrough",
|
|
63
|
+
render: (_node, children) => react_1.default.createElement("s", null, ...children)
|
|
64
|
+
};
|
|
65
|
+
exports.InlineCodeHandler = {
|
|
66
|
+
type: "InlineCode",
|
|
67
|
+
render: (node) => react_1.default.createElement("code", null, node.content || "")
|
|
68
|
+
};
|
|
69
|
+
//Media nodes
|
|
70
|
+
exports.LinkHandler = {
|
|
71
|
+
type: "Link",
|
|
72
|
+
render: (node) => react_1.default.createElement("a", { href: node.href, target: "_blank", rel: "noopener" }, node.text)
|
|
73
|
+
};
|
|
74
|
+
exports.ImageHandler = {
|
|
75
|
+
type: "Image",
|
|
76
|
+
render: (node) => react_1.default.createElement("img", {
|
|
77
|
+
src: node.src || "",
|
|
78
|
+
alt: node.alt || ""
|
|
79
|
+
})
|
|
80
|
+
};
|
|
81
|
+
//Leaf nodes
|
|
82
|
+
exports.HorizontalLineHandler = {
|
|
83
|
+
type: "HorizontalLine",
|
|
84
|
+
render: () => react_1.default.createElement("hr")
|
|
85
|
+
};
|
|
86
|
+
exports.TextHandler = {
|
|
87
|
+
type: "Text",
|
|
88
|
+
render: (node) => node.value || ""
|
|
89
|
+
};
|
|
90
|
+
//Table nodes
|
|
91
|
+
exports.TableHandler = {
|
|
92
|
+
type: "Table",
|
|
93
|
+
render: (node, children, ctx) => ctx.renderTable(node, children)
|
|
94
|
+
};
|
|
95
|
+
//For HTML
|
|
96
|
+
exports.HTMLBlockHandler = {
|
|
97
|
+
type: "HTMLBlock",
|
|
98
|
+
render: (node, _children, ctx) => {
|
|
99
|
+
const val = node.value || "";
|
|
100
|
+
return ctx.options.converterOptions?.allowDangerousHtml
|
|
101
|
+
? react_1.default.createElement("div", { dangerouslySetInnerHTML: { __html: val } })
|
|
102
|
+
: react_1.default.createElement("code", null, val);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
exports.HTMLInlineHandler = {
|
|
106
|
+
type: "HTMLInline",
|
|
107
|
+
render: (node, _children, ctx) => {
|
|
108
|
+
const val = node.value || "";
|
|
109
|
+
return ctx.options.converterOptions?.allowDangerousHtml
|
|
110
|
+
? react_1.default.createElement("span", { dangerouslySetInnerHTML: { __html: val } })
|
|
111
|
+
: react_1.default.createElement("code", null, val);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
//For footnote
|
|
115
|
+
exports.FootnoteRefHandler = {
|
|
116
|
+
type: "FootnoteRef",
|
|
117
|
+
render: (node, _children, ctx) => {
|
|
118
|
+
if (!node.id)
|
|
119
|
+
return null;
|
|
120
|
+
const idx = ctx.footnoteResolver.getUsedRefById(node.id);
|
|
121
|
+
return react_1.default.createElement("sup", { id: `fnref:${idx}` }, react_1.default.createElement("a", { href: `#fn:${idx}`, className: "footnote-ref" }, `[${idx}]`));
|
|
122
|
+
}
|
|
123
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { IRenderer } from "..";
|
|
3
|
+
import { MarkdownOptions } from "../../types/options";
|
|
4
|
+
import { ASTNode } from "../../types/parser";
|
|
5
|
+
import { FootnoteResolver } from "../../core/resolver/footnote-resolver";
|
|
6
|
+
export declare class ReactRenderer implements IRenderer<React.ReactNode> {
|
|
7
|
+
options: MarkdownOptions<React.ReactNode>;
|
|
8
|
+
footnoteResolver: FootnoteResolver;
|
|
9
|
+
private strategies;
|
|
10
|
+
constructor(footnoteResolver: FootnoteResolver, options?: MarkdownOptions<React.ReactNode>);
|
|
11
|
+
private registerDefaultStrategies;
|
|
12
|
+
render(node: ASTNode): React.ReactNode;
|
|
13
|
+
renderFootnotes(): React.ReactNode;
|
|
14
|
+
renderTable(node: ASTNode, children: React.ReactNode[]): React.ReactNode;
|
|
15
|
+
}
|