simple-customize-markdown-converter 1.2.1 → 1.3.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 +79 -0
- package/dist/core/lexer/handler.js +21 -21
- package/dist/core/lexer/index.d.ts +11 -4
- package/dist/core/lexer/index.js +22 -4
- package/dist/core/parser/index.d.ts +11 -4
- package/dist/core/parser/index.js +10 -1
- package/dist/index.d.ts +28 -2
- package/dist/index.js +39 -10
- package/dist/react.d.ts +28 -1
- package/dist/react.js +41 -12
- package/dist/renderers/default/index.d.ts +4 -2
- package/dist/renderers/default/index.js +6 -1
- package/dist/renderers/index.d.ts +3 -0
- package/dist/renderers/react/index.d.ts +4 -2
- package/dist/renderers/react/index.js +6 -1
- package/dist/types/converter.d.ts +14 -0
- package/dist/types/converter.js +23 -0
- package/dist/types/plugin.d.ts +51 -0
- package/dist/types/plugin.js +26 -0
- package/dist/types/token.d.ts +1 -1
- package/dist/utilities/tokenizer-utils.js +2 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -173,6 +173,85 @@ function App() {
|
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
```
|
|
176
|
+
|
|
177
|
+
## Plugin (v1.3.0+)
|
|
178
|
+
You can create a plugin to define custom sytax rule handler
|
|
179
|
+
- With default converter
|
|
180
|
+
```ts
|
|
181
|
+
import { DefaultMarkdownConverter } from 'simple-customize-markdown-converter';
|
|
182
|
+
|
|
183
|
+
const emojiPlugin = createPlugin<string, React.ReactNode>(
|
|
184
|
+
"Emoji",
|
|
185
|
+
"inline",
|
|
186
|
+
{
|
|
187
|
+
match: (lexer) => lexer.peek() === ":",
|
|
188
|
+
emit: (lexer) => {
|
|
189
|
+
lexer.next();
|
|
190
|
+
const value = lexer.readUntil(":");
|
|
191
|
+
lexer.listToken.push({ type: "Emoji", value });
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
execute: (parser, token) => {
|
|
196
|
+
parser.next(1);
|
|
197
|
+
return { type: "Emoji", value: token.value };
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
render: (node) => React.createElement(
|
|
202
|
+
"span",
|
|
203
|
+
{ className: `emoji emoji-${node.value}` },
|
|
204
|
+
"😲"
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
const converter = new DefaultMarkdownConverter({}, plugin).convert(input)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
- With React converter
|
|
213
|
+
```tsx
|
|
214
|
+
import { MarkdownComponent } from 'simple-customize-markdown-converter/react';
|
|
215
|
+
|
|
216
|
+
function App() {
|
|
217
|
+
const emojiPlugin = createPlugin<string, React.ReactNode>(
|
|
218
|
+
"Emoji",
|
|
219
|
+
"inline",
|
|
220
|
+
{
|
|
221
|
+
match: (lexer) => lexer.peek() === ":",
|
|
222
|
+
emit: (lexer) => {
|
|
223
|
+
lexer.next();
|
|
224
|
+
const value = lexer.readUntil(":");
|
|
225
|
+
lexer.listToken.push({ type: "Emoji", value });
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
execute: (parser, token) => {
|
|
230
|
+
parser.next(1);
|
|
231
|
+
return { type: "Emoji", value: token.value };
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
render: (node) => React.createElement(
|
|
236
|
+
"span",
|
|
237
|
+
{ className: `emoji emoji-${node.value}` },
|
|
238
|
+
"😲"
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
<MarkdownComponent
|
|
245
|
+
content="Hello :omg: world"
|
|
246
|
+
className="md-body"
|
|
247
|
+
options=options
|
|
248
|
+
plugin=[emojiPlugin]
|
|
249
|
+
/>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
|
|
176
255
|
## Security
|
|
177
256
|
By default, HTML tags in Markdown are escaped. To allow raw HTML, explicitly set `allowDangerousHtml: true` in `converterOptions`. Be sure only **enable** this for **trusted** content.
|
|
178
257
|
|
|
@@ -36,7 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.UnorderedListHandler = exports.TaskListHandler = exports.TableHandler = exports.StrikethroughHandler = exports.QuoteHandler = exports.OrderedListHandler = exports.NewLineHandler = exports.LinkHandler = exports.ItalicHandler = exports.InlineCodeHandler = exports.ImageHandler = exports.HtmlHandler = exports.HorizontalLineHandler = exports.HeaderHandler = exports.FootnoteRefHandler = exports.FootnoteDefHandler = exports.EscapeCharacterHandler = exports.EndListHandler = exports.CommentHandler = exports.CodeBlockHandler = exports.BoldHandler = void 0;
|
|
37
37
|
const utils = __importStar(require("../../utilities/tokenizer-utils"));
|
|
38
38
|
const EscapeCharacterHandler = {
|
|
39
|
-
|
|
39
|
+
type: "EscapeCharacter",
|
|
40
40
|
match: (lex) => lex.peek() === "\\" && lex.peek(1) !== undefined,
|
|
41
41
|
emit: (lex) => {
|
|
42
42
|
lex.next(1);
|
|
@@ -45,13 +45,13 @@ const EscapeCharacterHandler = {
|
|
|
45
45
|
};
|
|
46
46
|
exports.EscapeCharacterHandler = EscapeCharacterHandler;
|
|
47
47
|
const CommentHandler = {
|
|
48
|
-
|
|
48
|
+
type: "Comment",
|
|
49
49
|
match: (lex) => lex.startsWith("<!--"),
|
|
50
50
|
emit: (lex) => lex.readUntilMatchString("-->", true)
|
|
51
51
|
};
|
|
52
52
|
exports.CommentHandler = CommentHandler;
|
|
53
53
|
const HtmlHandler = {
|
|
54
|
-
|
|
54
|
+
type: "HTML",
|
|
55
55
|
match: (lex) => lex.peek() === "<",
|
|
56
56
|
emit: (lex) => {
|
|
57
57
|
//Handle comment
|
|
@@ -67,7 +67,7 @@ const HtmlHandler = {
|
|
|
67
67
|
};
|
|
68
68
|
exports.HtmlHandler = HtmlHandler;
|
|
69
69
|
const HorizontalLineHandler = {
|
|
70
|
-
|
|
70
|
+
type: "HorizontalLine",
|
|
71
71
|
match: (lex) => /^([-*_])\1{2,}$/.test(lex.peekUntil("\n").trim()) && lex.getLastToken()?.type === "NewLine",
|
|
72
72
|
emit: (lex) => {
|
|
73
73
|
lex.next(2); //Skip two first characters, remain will be skiped after loop
|
|
@@ -76,7 +76,7 @@ const HorizontalLineHandler = {
|
|
|
76
76
|
};
|
|
77
77
|
exports.HorizontalLineHandler = HorizontalLineHandler;
|
|
78
78
|
const CodeBlockHandler = {
|
|
79
|
-
|
|
79
|
+
type: "CodeBlock",
|
|
80
80
|
match: (lex) => lex.startsWith("```"),
|
|
81
81
|
emit: (lex) => {
|
|
82
82
|
let lang = "";
|
|
@@ -97,7 +97,7 @@ const CodeBlockHandler = {
|
|
|
97
97
|
};
|
|
98
98
|
exports.CodeBlockHandler = CodeBlockHandler;
|
|
99
99
|
const BoldHandler = {
|
|
100
|
-
|
|
100
|
+
type: "Bold",
|
|
101
101
|
match: (lex) => lex.startsWith("**"),
|
|
102
102
|
emit: (lex) => {
|
|
103
103
|
lex.listToken.push({ type: "Bold" });
|
|
@@ -106,7 +106,7 @@ const BoldHandler = {
|
|
|
106
106
|
};
|
|
107
107
|
exports.BoldHandler = BoldHandler;
|
|
108
108
|
const StrikethroughHandler = {
|
|
109
|
-
|
|
109
|
+
type: "Strikethrough",
|
|
110
110
|
match: (lex) => lex.startsWith("~~"),
|
|
111
111
|
emit: (lex) => {
|
|
112
112
|
lex.listToken.push({ type: "Strikethrough" });
|
|
@@ -116,7 +116,7 @@ const StrikethroughHandler = {
|
|
|
116
116
|
exports.StrikethroughHandler = StrikethroughHandler;
|
|
117
117
|
// Footnote
|
|
118
118
|
const FootnoteDefHandler = {
|
|
119
|
-
|
|
119
|
+
type: "FootnoteDef",
|
|
120
120
|
match: (lex) => lex.isStartOfLine() && /^\[\^[^\]]+\]:/.test(lex.peekUntil("\n")),
|
|
121
121
|
emit: (lex) => {
|
|
122
122
|
const line = lex.readUntil("\n");
|
|
@@ -130,7 +130,7 @@ const FootnoteDefHandler = {
|
|
|
130
130
|
};
|
|
131
131
|
exports.FootnoteDefHandler = FootnoteDefHandler;
|
|
132
132
|
const FootnoteRefHandler = {
|
|
133
|
-
|
|
133
|
+
type: "FootnoteRef",
|
|
134
134
|
match: (lex) => lex.startsWith("[^"),
|
|
135
135
|
emit: (lex) => {
|
|
136
136
|
lex.next(2); //Skip [^
|
|
@@ -141,25 +141,25 @@ const FootnoteRefHandler = {
|
|
|
141
141
|
exports.FootnoteRefHandler = FootnoteRefHandler;
|
|
142
142
|
//List
|
|
143
143
|
const TaskListHandler = {
|
|
144
|
-
|
|
144
|
+
type: "TaskList",
|
|
145
145
|
match: (lex) => lex.isStartOfLine() && /^(\s*)([-*+]) \[( |x|X)\] /.test(lex.peekUntil("\n")),
|
|
146
146
|
emit: (lex) => utils.handleList(lex, false, true)
|
|
147
147
|
};
|
|
148
148
|
exports.TaskListHandler = TaskListHandler;
|
|
149
149
|
const UnorderedListHandler = {
|
|
150
|
-
|
|
150
|
+
type: "UnorderList",
|
|
151
151
|
match: (lex) => lex.isStartOfLine() && /^(\s*)([-*+]) /.test(lex.peekUntil("\n")),
|
|
152
152
|
emit: (lex) => utils.handleList(lex, false, false)
|
|
153
153
|
};
|
|
154
154
|
exports.UnorderedListHandler = UnorderedListHandler;
|
|
155
155
|
const OrderedListHandler = {
|
|
156
|
-
|
|
156
|
+
type: "OrderedList",
|
|
157
157
|
match: (lex) => lex.isStartOfLine() && /^(\s*)(\d+)\. /.test(lex.peekUntil("\n")),
|
|
158
158
|
emit: (lex) => utils.handleList(lex, true, false)
|
|
159
159
|
};
|
|
160
160
|
exports.OrderedListHandler = OrderedListHandler;
|
|
161
161
|
const EndListHandler = {
|
|
162
|
-
|
|
162
|
+
type: "EndList",
|
|
163
163
|
match: (lex) => lex.listLevelFlag > 0 && lex.isStartOfLine() && !/^(\s*)([-+*]|\d+\.) /.test(lex.peekUntil("\n")),
|
|
164
164
|
emit: (lex) => {
|
|
165
165
|
while (lex.listLevelFlag > 0) {
|
|
@@ -170,14 +170,14 @@ const EndListHandler = {
|
|
|
170
170
|
exports.EndListHandler = EndListHandler;
|
|
171
171
|
//Table
|
|
172
172
|
const TableHandler = {
|
|
173
|
-
|
|
173
|
+
type: "Table",
|
|
174
174
|
match: (lex) => lex.isStartOfLine() && /^\s*\|.*\|\s*$/.test(lex.peekUntil("\n")),
|
|
175
175
|
emit: (lex) => utils.handleTable(lex)
|
|
176
176
|
};
|
|
177
177
|
exports.TableHandler = TableHandler;
|
|
178
178
|
//Other common syntax
|
|
179
179
|
const InlineCodeHandler = {
|
|
180
|
-
|
|
180
|
+
type: "InlineCode",
|
|
181
181
|
match: (lex) => lex.peek() === "`",
|
|
182
182
|
emit: (lex) => {
|
|
183
183
|
let content = "";
|
|
@@ -191,7 +191,7 @@ const InlineCodeHandler = {
|
|
|
191
191
|
};
|
|
192
192
|
exports.InlineCodeHandler = InlineCodeHandler;
|
|
193
193
|
const HeaderHandler = {
|
|
194
|
-
|
|
194
|
+
type: "Header",
|
|
195
195
|
match: (lex) => lex.peek() === "#",
|
|
196
196
|
emit: (lex) => {
|
|
197
197
|
let level = 0;
|
|
@@ -208,7 +208,7 @@ const HeaderHandler = {
|
|
|
208
208
|
};
|
|
209
209
|
exports.HeaderHandler = HeaderHandler;
|
|
210
210
|
const ItalicHandler = {
|
|
211
|
-
|
|
211
|
+
type: "Italic",
|
|
212
212
|
match: (lex) => lex.peek() === "*" || lex.peek() === "_",
|
|
213
213
|
emit: (lex) => {
|
|
214
214
|
lex.listToken.push({ type: "Italic" });
|
|
@@ -216,7 +216,7 @@ const ItalicHandler = {
|
|
|
216
216
|
};
|
|
217
217
|
exports.ItalicHandler = ItalicHandler;
|
|
218
218
|
const QuoteHandler = {
|
|
219
|
-
|
|
219
|
+
type: "Quote",
|
|
220
220
|
match: (lex) => lex.peek() === ">",
|
|
221
221
|
emit: (lex) => {
|
|
222
222
|
lex.listToken.push({ type: "Quote" });
|
|
@@ -224,7 +224,7 @@ const QuoteHandler = {
|
|
|
224
224
|
};
|
|
225
225
|
exports.QuoteHandler = QuoteHandler;
|
|
226
226
|
const LinkHandler = {
|
|
227
|
-
|
|
227
|
+
type: "Link",
|
|
228
228
|
match: (lex) => lex.peek() === "[",
|
|
229
229
|
emit: (lex) => {
|
|
230
230
|
lex.next(); //Skip [
|
|
@@ -242,7 +242,7 @@ const LinkHandler = {
|
|
|
242
242
|
};
|
|
243
243
|
exports.LinkHandler = LinkHandler;
|
|
244
244
|
const ImageHandler = {
|
|
245
|
-
|
|
245
|
+
type: "Image",
|
|
246
246
|
match: (lex) => lex.peek() === "!" && lex.peek(1) === "[",
|
|
247
247
|
emit: (lex) => {
|
|
248
248
|
lex.next(); //Skip !
|
|
@@ -263,7 +263,7 @@ const ImageHandler = {
|
|
|
263
263
|
};
|
|
264
264
|
exports.ImageHandler = ImageHandler;
|
|
265
265
|
const NewLineHandler = {
|
|
266
|
-
|
|
266
|
+
type: "NewLine",
|
|
267
267
|
match: (lex) => lex.peek() === "\n",
|
|
268
268
|
emit: (lex) => {
|
|
269
269
|
lex.listToken.push({ type: "NewLine" });
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Token } from "../../types/token";
|
|
1
|
+
import { Token, TokenizerStrategy } from "../../types/token";
|
|
2
2
|
export interface ILexer {
|
|
3
3
|
pos: number;
|
|
4
4
|
input: string;
|
|
5
5
|
listToken: Token[];
|
|
6
6
|
listLevelFlag: number;
|
|
7
|
+
strategies: TokenizerStrategy[];
|
|
7
8
|
peek(offset?: number): string | null;
|
|
8
9
|
next(amount?: number): void;
|
|
9
10
|
startsWith(str: string): boolean;
|
|
@@ -14,14 +15,19 @@ export interface ILexer {
|
|
|
14
15
|
isEndOfFile(): boolean;
|
|
15
16
|
isStartOfLine(): boolean;
|
|
16
17
|
getLastToken(): Token;
|
|
18
|
+
registerStrategy(strategy: TokenizerStrategy): void;
|
|
17
19
|
}
|
|
18
|
-
export
|
|
20
|
+
export declare class Lexer implements ILexer {
|
|
19
21
|
input: string;
|
|
20
22
|
pos: number;
|
|
21
23
|
listToken: Token[];
|
|
22
24
|
listLevelFlag: number;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
strategies: TokenizerStrategy[];
|
|
26
|
+
private coreStrategy;
|
|
27
|
+
private pluginStrategy;
|
|
28
|
+
private defaultStrategy;
|
|
29
|
+
constructor(input: string, pluginStrategy?: TokenizerStrategy[]);
|
|
30
|
+
private setUpStrategy;
|
|
25
31
|
setInput(input: string): void;
|
|
26
32
|
peek(offset?: number): string | null;
|
|
27
33
|
next(amount?: number): void;
|
|
@@ -33,6 +39,7 @@ export default class Lexer implements ILexer {
|
|
|
33
39
|
peekUntilByOffset(offset: number): string;
|
|
34
40
|
isStartOfLine(): boolean;
|
|
35
41
|
readUntilMatchString(str: string, isConsume?: boolean): string;
|
|
42
|
+
registerStrategy(strategy: TokenizerStrategy): void;
|
|
36
43
|
/**
|
|
37
44
|
* Tokenize the markdown into a list of tokens.
|
|
38
45
|
* @param isEof - `True` when input is whole markdown, `False` if input is just a part of markdown.
|
package/dist/core/lexer/index.js
CHANGED
|
@@ -33,19 +33,22 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.Lexer = void 0;
|
|
36
37
|
const Handlers = __importStar(require("./handler"));
|
|
37
38
|
const utils = __importStar(require("../../utilities/tokenizer-utils"));
|
|
38
39
|
class Lexer {
|
|
39
|
-
constructor(input) {
|
|
40
|
+
constructor(input, pluginStrategy = []) {
|
|
40
41
|
this.pos = 0;
|
|
41
42
|
this.listToken = [];
|
|
42
43
|
// Flag for handle special syntax
|
|
43
44
|
this.listLevelFlag = 0;
|
|
44
|
-
this.
|
|
45
|
-
this.strategies = [
|
|
45
|
+
this.coreStrategy = [
|
|
46
46
|
Handlers.EscapeCharacterHandler,
|
|
47
47
|
Handlers.CommentHandler,
|
|
48
48
|
Handlers.HtmlHandler,
|
|
49
|
+
];
|
|
50
|
+
this.pluginStrategy = [];
|
|
51
|
+
this.defaultStrategy = [
|
|
49
52
|
Handlers.HorizontalLineHandler,
|
|
50
53
|
Handlers.CodeBlockHandler,
|
|
51
54
|
Handlers.BoldHandler,
|
|
@@ -65,6 +68,17 @@ class Lexer {
|
|
|
65
68
|
Handlers.ImageHandler,
|
|
66
69
|
Handlers.NewLineHandler,
|
|
67
70
|
];
|
|
71
|
+
this.input = input;
|
|
72
|
+
this.pluginStrategy = pluginStrategy;
|
|
73
|
+
this.strategies = [];
|
|
74
|
+
this.setUpStrategy();
|
|
75
|
+
}
|
|
76
|
+
setUpStrategy() {
|
|
77
|
+
this.strategies = [
|
|
78
|
+
...this.coreStrategy,
|
|
79
|
+
...this.pluginStrategy,
|
|
80
|
+
...this.defaultStrategy
|
|
81
|
+
];
|
|
68
82
|
}
|
|
69
83
|
//Reset input and other attribute
|
|
70
84
|
setInput(input) {
|
|
@@ -146,6 +160,10 @@ class Lexer {
|
|
|
146
160
|
}
|
|
147
161
|
return result;
|
|
148
162
|
}
|
|
163
|
+
registerStrategy(strategy) {
|
|
164
|
+
this.pluginStrategy.push(strategy);
|
|
165
|
+
this.setUpStrategy();
|
|
166
|
+
}
|
|
149
167
|
/**
|
|
150
168
|
* Tokenize the markdown into a list of tokens.
|
|
151
169
|
* @param isEof - `True` when input is whole markdown, `False` if input is just a part of markdown.
|
|
@@ -174,4 +192,4 @@ class Lexer {
|
|
|
174
192
|
return this.listToken;
|
|
175
193
|
}
|
|
176
194
|
}
|
|
177
|
-
exports.
|
|
195
|
+
exports.Lexer = Lexer;
|
|
@@ -1,26 +1,33 @@
|
|
|
1
|
-
import { ASTNode } from '../../types/parser';
|
|
1
|
+
import { ASTNode, ParsingStrategy } from '../../types/parser';
|
|
2
2
|
import { Token } from '../../types/token';
|
|
3
3
|
import { FootnoteResolver } from '../resolver/footnote-resolver';
|
|
4
4
|
export interface IParser {
|
|
5
5
|
listToken: Token[];
|
|
6
6
|
pos: number;
|
|
7
|
+
inlineStrategies: Map<string, ParsingStrategy>;
|
|
8
|
+
blockStrategies: Map<string, ParsingStrategy>;
|
|
7
9
|
footNoteResolver: FootnoteResolver;
|
|
8
10
|
peek(offset: number): Token | null;
|
|
9
11
|
next(amount: number): void;
|
|
10
12
|
isEnd(): boolean;
|
|
11
13
|
parseBlocks(): ASTNode[];
|
|
12
14
|
parseInlineUntil(stopType: Token["type"] | Token["type"][], isConsumeStopToken: boolean): ASTNode[];
|
|
15
|
+
registerStrategy(strategy: ParsingStrategy, type: "block" | "inline"): void;
|
|
13
16
|
}
|
|
14
17
|
export declare class Parser implements IParser {
|
|
15
18
|
listToken: Token[];
|
|
16
19
|
pos: number;
|
|
17
20
|
footNoteResolver: FootnoteResolver;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
constructor(listToken: Token[], footNoteResolver: FootnoteResolver
|
|
21
|
+
inlineStrategies: Map<string, ParsingStrategy>;
|
|
22
|
+
blockStrategies: Map<string, ParsingStrategy>;
|
|
23
|
+
constructor(listToken: Token[], footNoteResolver: FootnoteResolver, plugin?: {
|
|
24
|
+
type: 'block' | 'inline';
|
|
25
|
+
strategy: ParsingStrategy;
|
|
26
|
+
}[]);
|
|
21
27
|
peek(offset?: number): Token | null;
|
|
22
28
|
next(amount?: number): void;
|
|
23
29
|
isEnd(): boolean;
|
|
30
|
+
registerStrategy(strategy: ParsingStrategy, type: 'block' | 'inline'): void;
|
|
24
31
|
/**
|
|
25
32
|
* Parse a list token to a node
|
|
26
33
|
* @return A parsed abstract syntax tree (AST)
|
|
@@ -36,7 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.Parser = void 0;
|
|
37
37
|
const Handler = __importStar(require("./handler"));
|
|
38
38
|
class Parser {
|
|
39
|
-
constructor(listToken, footNoteResolver) {
|
|
39
|
+
constructor(listToken, footNoteResolver, plugin = []) {
|
|
40
40
|
this.pos = 0;
|
|
41
41
|
this.listToken = listToken;
|
|
42
42
|
this.footNoteResolver = footNoteResolver;
|
|
@@ -61,6 +61,8 @@ class Parser {
|
|
|
61
61
|
Handler.HtmlInlineHandler,
|
|
62
62
|
Handler.FootnoteRefHandler,
|
|
63
63
|
].map(ele => [ele.type, ele]));
|
|
64
|
+
if (plugin.length > 0)
|
|
65
|
+
plugin.forEach(p => this.registerStrategy(p.strategy, p.type));
|
|
64
66
|
}
|
|
65
67
|
peek(offset = 0) {
|
|
66
68
|
const i = this.pos + offset;
|
|
@@ -72,6 +74,13 @@ class Parser {
|
|
|
72
74
|
isEnd() {
|
|
73
75
|
return this.peek()?.type === "EOF";
|
|
74
76
|
}
|
|
77
|
+
registerStrategy(strategy, type) {
|
|
78
|
+
if (type === "block") {
|
|
79
|
+
this.blockStrategies.set(strategy.type, strategy);
|
|
80
|
+
}
|
|
81
|
+
else
|
|
82
|
+
this.inlineStrategies.set(strategy.type, strategy);
|
|
83
|
+
}
|
|
75
84
|
/**
|
|
76
85
|
* Parse a list token to a node
|
|
77
86
|
* @return A parsed abstract syntax tree (AST)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
import { ILexer, Lexer } from "./core/lexer";
|
|
2
|
+
import { IParser, Parser } from "./core/parser";
|
|
3
|
+
import { IRenderer } from "./renderers/index";
|
|
4
|
+
import { DefaultRenderer } from "./renderers/default";
|
|
1
5
|
import { MarkdownOptions } from "./types/options";
|
|
2
6
|
import { Token, TokenizerStrategy } from './types/token';
|
|
3
7
|
import { ASTNode, ParsingStrategy } from './types/parser';
|
|
4
8
|
import { RenderStrategy } from './types/renderer';
|
|
5
|
-
|
|
9
|
+
import { MarkdownPlugin } from "./types/plugin";
|
|
10
|
+
import { BaseConverter } from "./types/converter";
|
|
11
|
+
export { MarkdownOptions, Token, TokenizerStrategy, ILexer, IParser, IRenderer, Lexer, Parser, DefaultRenderer, ASTNode, ParsingStrategy, RenderStrategy, MarkdownPlugin, };
|
|
6
12
|
/**
|
|
7
13
|
* Convert a Markdown string into HTML.
|
|
8
14
|
* @param input - The Markdown source string
|
|
@@ -15,4 +21,24 @@ export { MarkdownOptions, Token, TokenizerStrategy, ASTNode, ParsingStrategy, Re
|
|
|
15
21
|
* // => <p>Hello <strong>world</strong></p>
|
|
16
22
|
* ```
|
|
17
23
|
*/
|
|
18
|
-
export declare function convertMarkdownToHTML(input: string, options?: MarkdownOptions<string>): string;
|
|
24
|
+
export declare function convertMarkdownToHTML(input: string, options?: MarkdownOptions<string>, plugin?: MarkdownPlugin<string, string>[]): string;
|
|
25
|
+
/**
|
|
26
|
+
* Default Markdown converter that outputs a standard HTML string
|
|
27
|
+
* @extends BaseConverter<string>
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const converter = new DefaultMarkdownConverter(
|
|
31
|
+
* {
|
|
32
|
+
* renderOptions: {
|
|
33
|
+
* className: { Header: "my-title" }
|
|
34
|
+
* }
|
|
35
|
+
* },
|
|
36
|
+
* [MyCustomPlugin]
|
|
37
|
+
* );
|
|
38
|
+
* const html = converter.convert("# Hello");
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare class DefaultMarkdownConverter extends BaseConverter<string> {
|
|
42
|
+
constructor(options?: MarkdownOptions<string>, plugin?: MarkdownPlugin<string, string>[]);
|
|
43
|
+
convert(input: string): string;
|
|
44
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DefaultMarkdownConverter = exports.DefaultRenderer = exports.Parser = exports.Lexer = void 0;
|
|
6
4
|
exports.convertMarkdownToHTML = convertMarkdownToHTML;
|
|
7
|
-
const lexer_1 =
|
|
5
|
+
const lexer_1 = require("./core/lexer");
|
|
6
|
+
Object.defineProperty(exports, "Lexer", { enumerable: true, get: function () { return lexer_1.Lexer; } });
|
|
8
7
|
const parser_1 = require("./core/parser");
|
|
9
|
-
|
|
8
|
+
Object.defineProperty(exports, "Parser", { enumerable: true, get: function () { return parser_1.Parser; } });
|
|
10
9
|
const default_1 = require("./renderers/default");
|
|
10
|
+
Object.defineProperty(exports, "DefaultRenderer", { enumerable: true, get: function () { return default_1.DefaultRenderer; } });
|
|
11
|
+
const converter_1 = require("./types/converter");
|
|
11
12
|
/**
|
|
12
13
|
* Convert a Markdown string into HTML.
|
|
13
14
|
* @param input - The Markdown source string
|
|
@@ -23,9 +24,37 @@ const default_1 = require("./renderers/default");
|
|
|
23
24
|
function convertMarkdownToHTML(input, options = {
|
|
24
25
|
renderOptions: {},
|
|
25
26
|
converterOptions: { allowDangerousHtml: false }
|
|
26
|
-
}) {
|
|
27
|
-
|
|
28
|
-
const footNoteResolver = new footnote_resolver_1.FootnoteResolver();
|
|
29
|
-
const nodes = new parser_1.Parser(tokens, footNoteResolver).parse();
|
|
30
|
-
return new default_1.DefaultRenderer(footNoteResolver, options).render(nodes);
|
|
27
|
+
}, plugin = []) {
|
|
28
|
+
return new DefaultMarkdownConverter(options, plugin).convert(input);
|
|
31
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Default Markdown converter that outputs a standard HTML string
|
|
32
|
+
* @extends BaseConverter<string>
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const converter = new DefaultMarkdownConverter(
|
|
36
|
+
* {
|
|
37
|
+
* renderOptions: {
|
|
38
|
+
* className: { Header: "my-title" }
|
|
39
|
+
* }
|
|
40
|
+
* },
|
|
41
|
+
* [MyCustomPlugin]
|
|
42
|
+
* );
|
|
43
|
+
* const html = converter.convert("# Hello");
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
class DefaultMarkdownConverter extends converter_1.BaseConverter {
|
|
47
|
+
constructor(options = {
|
|
48
|
+
renderOptions: {},
|
|
49
|
+
converterOptions: { allowDangerousHtml: false }
|
|
50
|
+
}, plugin = []) {
|
|
51
|
+
super(options, plugin);
|
|
52
|
+
}
|
|
53
|
+
convert(input) {
|
|
54
|
+
const tokens = this.getTokens(input);
|
|
55
|
+
const nodes = this.getNodes(tokens);
|
|
56
|
+
const renderer = new default_1.DefaultRenderer(this.footnoteResolver, this.options, this.plugin.map(p => p.renderer));
|
|
57
|
+
return renderer.render(nodes);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.DefaultMarkdownConverter = DefaultMarkdownConverter;
|
package/dist/react.d.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { ReactRenderer } from "./renderers/react";
|
|
2
3
|
import { MarkdownOptions } from "./types/options";
|
|
4
|
+
import { BaseConverter } from "./types/converter";
|
|
5
|
+
import { MarkdownPlugin } from "./types/plugin";
|
|
6
|
+
export { ReactRenderer };
|
|
3
7
|
/**
|
|
4
8
|
* Convert a Markdown string into a ReactNode.
|
|
5
9
|
* @param input - The Markdown source string
|
|
6
10
|
* @param renderOptions - Optional rendering options
|
|
7
11
|
* @param options - Optional handle options
|
|
12
|
+
* @param [plugin=[]] - Optional plugin for additional render rules
|
|
8
13
|
* @returns The rendered `React.ReactNode` ready to be rendered into a React component.
|
|
9
14
|
*
|
|
10
15
|
* @example
|
|
@@ -13,13 +18,14 @@ import { MarkdownOptions } from "./types/options";
|
|
|
13
18
|
* return <div>{node}</div>;
|
|
14
19
|
* ```
|
|
15
20
|
*/
|
|
16
|
-
export declare function convertMarkdownToReactNode(input: string, options?: MarkdownOptions<React.ReactNode>): React.ReactNode;
|
|
21
|
+
export declare function convertMarkdownToReactNode(input: string, options?: MarkdownOptions<React.ReactNode>, plugin?: MarkdownPlugin<string, React.ReactNode>[]): React.ReactNode;
|
|
17
22
|
/**
|
|
18
23
|
* A React commponent that renders Markdown content.
|
|
19
24
|
* Using `React.useMemo` to ensure performance and prevent unnecessary re-render.
|
|
20
25
|
* @param props.content - The Markdown source to render.
|
|
21
26
|
* @param props.options - Optional configuration for the renderer.
|
|
22
27
|
* @param props.className - Optional CSS classes for the wrapping `div` element.
|
|
28
|
+
* @param props.plugin - Optional plugin for additional syntax handler.
|
|
23
29
|
* @example
|
|
24
30
|
* ```tsx
|
|
25
31
|
* <MarkdownComponent
|
|
@@ -33,4 +39,25 @@ export declare const MarkdownComponent: React.FC<{
|
|
|
33
39
|
content: string;
|
|
34
40
|
options?: MarkdownOptions<React.ReactNode>;
|
|
35
41
|
className?: string;
|
|
42
|
+
plugin?: MarkdownPlugin<string, React.ReactNode>[];
|
|
36
43
|
}>;
|
|
44
|
+
/**
|
|
45
|
+
* React Markdown converter that outputs a React.ReactNode
|
|
46
|
+
* @extends BaseConverter<React.ReactNode>
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* const converter = new ReactMarkdownConverter(
|
|
50
|
+
* {
|
|
51
|
+
* renderOptions: {
|
|
52
|
+
* className: { Header: "my-title" }
|
|
53
|
+
* }
|
|
54
|
+
* },
|
|
55
|
+
* [MyCustomPlugin]
|
|
56
|
+
* );
|
|
57
|
+
* const html = converter.convert("# Hello");
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export declare class ReactMarkdownConverter extends BaseConverter<React.ReactNode> {
|
|
61
|
+
constructor(options?: MarkdownOptions<React.ReactNode>, plugin?: MarkdownPlugin<string, React.ReactNode>[]);
|
|
62
|
+
convert(input: string): React.ReactNode;
|
|
63
|
+
}
|
package/dist/react.js
CHANGED
|
@@ -3,18 +3,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.MarkdownComponent = void 0;
|
|
6
|
+
exports.ReactMarkdownConverter = exports.MarkdownComponent = exports.ReactRenderer = void 0;
|
|
7
7
|
exports.convertMarkdownToReactNode = convertMarkdownToReactNode;
|
|
8
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
9
|
const react_2 = require("./renderers/react");
|
|
10
|
+
Object.defineProperty(exports, "ReactRenderer", { enumerable: true, get: function () { return react_2.ReactRenderer; } });
|
|
11
|
+
const converter_1 = require("./types/converter");
|
|
13
12
|
/**
|
|
14
13
|
* Convert a Markdown string into a ReactNode.
|
|
15
14
|
* @param input - The Markdown source string
|
|
16
15
|
* @param renderOptions - Optional rendering options
|
|
17
16
|
* @param options - Optional handle options
|
|
17
|
+
* @param [plugin=[]] - Optional plugin for additional render rules
|
|
18
18
|
* @returns The rendered `React.ReactNode` ready to be rendered into a React component.
|
|
19
19
|
*
|
|
20
20
|
* @example
|
|
@@ -26,11 +26,8 @@ const react_2 = require("./renderers/react");
|
|
|
26
26
|
function convertMarkdownToReactNode(input, options = {
|
|
27
27
|
renderOptions: {},
|
|
28
28
|
converterOptions: { allowDangerousHtml: false }
|
|
29
|
-
}) {
|
|
30
|
-
|
|
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);
|
|
29
|
+
}, plugin = []) {
|
|
30
|
+
return new ReactMarkdownConverter(options, plugin).convert(input);
|
|
34
31
|
}
|
|
35
32
|
/**
|
|
36
33
|
* A React commponent that renders Markdown content.
|
|
@@ -38,6 +35,7 @@ function convertMarkdownToReactNode(input, options = {
|
|
|
38
35
|
* @param props.content - The Markdown source to render.
|
|
39
36
|
* @param props.options - Optional configuration for the renderer.
|
|
40
37
|
* @param props.className - Optional CSS classes for the wrapping `div` element.
|
|
38
|
+
* @param props.plugin - Optional plugin for additional syntax handler.
|
|
41
39
|
* @example
|
|
42
40
|
* ```tsx
|
|
43
41
|
* <MarkdownComponent
|
|
@@ -47,10 +45,41 @@ function convertMarkdownToReactNode(input, options = {
|
|
|
47
45
|
* />
|
|
48
46
|
* ```
|
|
49
47
|
*/
|
|
50
|
-
const MarkdownComponent = ({ content, className, options }) => {
|
|
48
|
+
const MarkdownComponent = ({ content, className, options, plugin }) => {
|
|
51
49
|
const rendered = react_1.default.useMemo(() => {
|
|
52
|
-
return
|
|
53
|
-
}, [content]);
|
|
50
|
+
return new ReactMarkdownConverter(options, plugin).convert(content);
|
|
51
|
+
}, [content, options, plugin]);
|
|
54
52
|
return react_1.default.createElement("div", { className }, rendered);
|
|
55
53
|
};
|
|
56
54
|
exports.MarkdownComponent = MarkdownComponent;
|
|
55
|
+
/**
|
|
56
|
+
* React Markdown converter that outputs a React.ReactNode
|
|
57
|
+
* @extends BaseConverter<React.ReactNode>
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* const converter = new ReactMarkdownConverter(
|
|
61
|
+
* {
|
|
62
|
+
* renderOptions: {
|
|
63
|
+
* className: { Header: "my-title" }
|
|
64
|
+
* }
|
|
65
|
+
* },
|
|
66
|
+
* [MyCustomPlugin]
|
|
67
|
+
* );
|
|
68
|
+
* const html = converter.convert("# Hello");
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
class ReactMarkdownConverter extends converter_1.BaseConverter {
|
|
72
|
+
constructor(options = {
|
|
73
|
+
renderOptions: {},
|
|
74
|
+
converterOptions: { allowDangerousHtml: false }
|
|
75
|
+
}, plugin = []) {
|
|
76
|
+
super(options, plugin);
|
|
77
|
+
}
|
|
78
|
+
convert(input) {
|
|
79
|
+
const tokens = this.getTokens(input);
|
|
80
|
+
const nodes = this.getNodes(tokens);
|
|
81
|
+
const renderer = new react_2.ReactRenderer(this.footnoteResolver, this.options, this.plugin.map(p => p.renderer));
|
|
82
|
+
return renderer.render(nodes);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.ReactMarkdownConverter = ReactMarkdownConverter;
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { IRenderer } from "..";
|
|
2
2
|
import { MarkdownOptions } from "../../types/options";
|
|
3
3
|
import { ASTNode } from "../../types/parser";
|
|
4
|
+
import { RenderStrategy } from "../../types/renderer";
|
|
4
5
|
import { FootnoteResolver } from "../../core/resolver/footnote-resolver";
|
|
5
6
|
export declare class DefaultRenderer implements IRenderer<string> {
|
|
6
7
|
options: MarkdownOptions<string>;
|
|
7
8
|
footnoteResolver: FootnoteResolver;
|
|
8
|
-
|
|
9
|
-
constructor(footnoteResolver: FootnoteResolver, options?: MarkdownOptions<string>);
|
|
9
|
+
strategies: Map<string, RenderStrategy<string>>;
|
|
10
|
+
constructor(footnoteResolver: FootnoteResolver, options?: MarkdownOptions<string>, plugin?: RenderStrategy<string>[]);
|
|
10
11
|
private registerDefaultStrategies;
|
|
11
12
|
render(node: ASTNode): string;
|
|
12
13
|
renderFootnotes(): string;
|
|
13
14
|
renderTable(node: ASTNode, children: string[]): string;
|
|
15
|
+
registerStrategy(strategy: RenderStrategy<string>): void;
|
|
14
16
|
}
|
|
@@ -37,11 +37,13 @@ exports.DefaultRenderer = void 0;
|
|
|
37
37
|
const Handlers = __importStar(require("./handler"));
|
|
38
38
|
const renderer_utils_1 = require("../../utilities/renderer-utils");
|
|
39
39
|
class DefaultRenderer {
|
|
40
|
-
constructor(footnoteResolver, options = {}) {
|
|
40
|
+
constructor(footnoteResolver, options = {}, plugin = []) {
|
|
41
41
|
this.strategies = new Map();
|
|
42
42
|
this.footnoteResolver = footnoteResolver;
|
|
43
43
|
this.options = options;
|
|
44
44
|
this.registerDefaultStrategies();
|
|
45
|
+
if (plugin.length > 0)
|
|
46
|
+
plugin.forEach(p => this.registerStrategy(p));
|
|
45
47
|
}
|
|
46
48
|
registerDefaultStrategies() {
|
|
47
49
|
const listDefaultStrategy = [
|
|
@@ -115,5 +117,8 @@ class DefaultRenderer {
|
|
|
115
117
|
else
|
|
116
118
|
return `<p${cls ? ` class="${cls}"` : ""}>${children.join("\n")}</p>`;
|
|
117
119
|
}
|
|
120
|
+
registerStrategy(strategy) {
|
|
121
|
+
this.strategies.set(strategy.type, strategy);
|
|
122
|
+
}
|
|
118
123
|
}
|
|
119
124
|
exports.DefaultRenderer = DefaultRenderer;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { ASTNode } from "../types/parser";
|
|
2
2
|
import { MarkdownOptions } from '../types/options/index';
|
|
3
3
|
import { FootnoteResolver } from "../core/resolver/footnote-resolver";
|
|
4
|
+
import { RenderStrategy } from "../types/renderer";
|
|
4
5
|
export interface IRenderer<TOutput> {
|
|
5
6
|
options: MarkdownOptions<TOutput>;
|
|
6
7
|
footnoteResolver: FootnoteResolver;
|
|
8
|
+
strategies: Map<string, RenderStrategy<TOutput>>;
|
|
7
9
|
render(node: ASTNode): TOutput;
|
|
8
10
|
renderTable(node: ASTNode, children: TOutput[]): TOutput;
|
|
9
11
|
renderFootnotes(): TOutput;
|
|
12
|
+
registerStrategy(strategy: RenderStrategy<TOutput>): void;
|
|
10
13
|
}
|
|
@@ -2,14 +2,16 @@ import React from "react";
|
|
|
2
2
|
import { IRenderer } from "..";
|
|
3
3
|
import { MarkdownOptions } from "../../types/options";
|
|
4
4
|
import { ASTNode } from "../../types/parser";
|
|
5
|
+
import { RenderStrategy } from "../../types/renderer";
|
|
5
6
|
import { FootnoteResolver } from "../../core/resolver/footnote-resolver";
|
|
6
7
|
export declare class ReactRenderer implements IRenderer<React.ReactNode> {
|
|
7
8
|
options: MarkdownOptions<React.ReactNode>;
|
|
8
9
|
footnoteResolver: FootnoteResolver;
|
|
9
|
-
|
|
10
|
-
constructor(footnoteResolver: FootnoteResolver, options?: MarkdownOptions<React.ReactNode>);
|
|
10
|
+
strategies: Map<string, RenderStrategy<React.ReactNode>>;
|
|
11
|
+
constructor(footnoteResolver: FootnoteResolver, options?: MarkdownOptions<React.ReactNode>, plugin?: RenderStrategy<React.ReactNode>[]);
|
|
11
12
|
private registerDefaultStrategies;
|
|
12
13
|
render(node: ASTNode): React.ReactNode;
|
|
13
14
|
renderFootnotes(): React.ReactNode;
|
|
14
15
|
renderTable(node: ASTNode, children: React.ReactNode[]): React.ReactNode;
|
|
16
|
+
registerStrategy(strategy: RenderStrategy<React.ReactNode>): void;
|
|
15
17
|
}
|
|
@@ -41,11 +41,13 @@ const react_1 = __importDefault(require("react"));
|
|
|
41
41
|
const Handlers = __importStar(require("./handler"));
|
|
42
42
|
const renderer_utils_1 = require("../../utilities/renderer-utils");
|
|
43
43
|
class ReactRenderer {
|
|
44
|
-
constructor(footnoteResolver, options = {}) {
|
|
44
|
+
constructor(footnoteResolver, options = {}, plugin = []) {
|
|
45
45
|
this.strategies = new Map();
|
|
46
46
|
this.footnoteResolver = footnoteResolver;
|
|
47
47
|
this.options = options;
|
|
48
48
|
this.registerDefaultStrategies();
|
|
49
|
+
if (plugin.length > 0)
|
|
50
|
+
plugin.forEach(p => this.registerStrategy(p));
|
|
49
51
|
}
|
|
50
52
|
registerDefaultStrategies() {
|
|
51
53
|
const listDefaultStrategy = [
|
|
@@ -120,5 +122,8 @@ class ReactRenderer {
|
|
|
120
122
|
else
|
|
121
123
|
return react_1.default.createElement("p", { className: (0, renderer_utils_1.getClassName)(this, node), }, ...children);
|
|
122
124
|
}
|
|
125
|
+
registerStrategy(strategy) {
|
|
126
|
+
this.strategies.set(strategy.type, strategy);
|
|
127
|
+
}
|
|
123
128
|
}
|
|
124
129
|
exports.ReactRenderer = ReactRenderer;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { FootnoteResolver } from "../core/resolver/footnote-resolver";
|
|
2
|
+
import { MarkdownOptions } from "./options";
|
|
3
|
+
import { ASTNode } from "./parser";
|
|
4
|
+
import { MarkdownPlugin } from "./plugin";
|
|
5
|
+
import { Token } from "./token";
|
|
6
|
+
export declare abstract class BaseConverter<TOutput> {
|
|
7
|
+
protected options: MarkdownOptions<TOutput>;
|
|
8
|
+
protected plugin: MarkdownPlugin<string, TOutput>[];
|
|
9
|
+
protected footnoteResolver: FootnoteResolver;
|
|
10
|
+
constructor(options: MarkdownOptions<TOutput>, plugin: MarkdownPlugin<string, TOutput>[]);
|
|
11
|
+
protected getTokens(input: string): Token[];
|
|
12
|
+
protected getNodes(tokens: Token[]): ASTNode;
|
|
13
|
+
abstract convert(input: string): TOutput;
|
|
14
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BaseConverter = void 0;
|
|
4
|
+
const lexer_1 = require("../core/lexer");
|
|
5
|
+
const parser_1 = require("../core/parser");
|
|
6
|
+
const footnote_resolver_1 = require("../core/resolver/footnote-resolver");
|
|
7
|
+
class BaseConverter {
|
|
8
|
+
constructor(options, plugin) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
this.plugin = plugin;
|
|
11
|
+
this.footnoteResolver = new footnote_resolver_1.FootnoteResolver();
|
|
12
|
+
}
|
|
13
|
+
getTokens(input) {
|
|
14
|
+
return new lexer_1.Lexer(input, this.plugin.map(p => p.tokenizer)).tokenize();
|
|
15
|
+
}
|
|
16
|
+
getNodes(tokens) {
|
|
17
|
+
return new parser_1.Parser(tokens, this.footnoteResolver, this.plugin.map(p => ({
|
|
18
|
+
type: p.type,
|
|
19
|
+
strategy: p.parser
|
|
20
|
+
}))).parse();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.BaseConverter = BaseConverter;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { TokenizerStrategy } from "./token";
|
|
2
|
+
import { ParsingStrategy } from "./parser";
|
|
3
|
+
import { RenderStrategy } from "./renderer";
|
|
4
|
+
/**
|
|
5
|
+
* Representing a custom plugin for the Markdown converter.
|
|
6
|
+
* It allow to define new syntax by hooking into the `Lexing`, `Parsing` and `Rendering` stages.
|
|
7
|
+
* @template TOutput - The type of final rendered output
|
|
8
|
+
*/
|
|
9
|
+
export interface MarkdownPlugin<T extends string, TOutput> {
|
|
10
|
+
/**
|
|
11
|
+
* Unique identifier for the plugin
|
|
12
|
+
*/
|
|
13
|
+
name: T;
|
|
14
|
+
/**
|
|
15
|
+
* Define the context of plugin
|
|
16
|
+
*/
|
|
17
|
+
type: "block" | "inline";
|
|
18
|
+
/**
|
|
19
|
+
* Strategy for the Lexer.
|
|
20
|
+
* The `type` property of this property must be same as `name` property
|
|
21
|
+
*/
|
|
22
|
+
tokenizer: TokenizerStrategy & {
|
|
23
|
+
type: T;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Strategy for the Parser.
|
|
27
|
+
* The `type` property of this property must be same as `name` property
|
|
28
|
+
*/
|
|
29
|
+
parser: ParsingStrategy & {
|
|
30
|
+
type: T;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Strategy for the Renderer.
|
|
34
|
+
* The `type` property of this property must be same as `name` property
|
|
35
|
+
*/
|
|
36
|
+
renderer: RenderStrategy<TOutput> & {
|
|
37
|
+
type: T;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A helper function to create a plugin
|
|
42
|
+
* @template T - The literal string type for the plugin name.
|
|
43
|
+
* @template TOutput - The output type.
|
|
44
|
+
* @param name - Name of plugin
|
|
45
|
+
* @param type - Context of plugin, determine for parser processing
|
|
46
|
+
* @param tokenizer - Tokenizer strategy for Lexer
|
|
47
|
+
* @param parser - Parser strategy for Parser
|
|
48
|
+
* @param renderer - Render strategy for Renderer
|
|
49
|
+
* @returns - A complete Markdown plugin
|
|
50
|
+
*/
|
|
51
|
+
export declare function createPlugin<T extends string, TOutput>(name: T, type: "block" | "inline", tokenizer: Omit<TokenizerStrategy, "type">, parser: Omit<ParsingStrategy, "type">, renderer: Omit<RenderStrategy<TOutput>, "type">): MarkdownPlugin<T, TOutput>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createPlugin = createPlugin;
|
|
4
|
+
/**
|
|
5
|
+
* A helper function to create a plugin
|
|
6
|
+
* @template T - The literal string type for the plugin name.
|
|
7
|
+
* @template TOutput - The output type.
|
|
8
|
+
* @param name - Name of plugin
|
|
9
|
+
* @param type - Context of plugin, determine for parser processing
|
|
10
|
+
* @param tokenizer - Tokenizer strategy for Lexer
|
|
11
|
+
* @param parser - Parser strategy for Parser
|
|
12
|
+
* @param renderer - Render strategy for Renderer
|
|
13
|
+
* @returns - A complete Markdown plugin
|
|
14
|
+
*/
|
|
15
|
+
function createPlugin(name, type, tokenizer, parser, renderer) {
|
|
16
|
+
const finalTokenizer = Object.assign(tokenizer, { type: name });
|
|
17
|
+
const finalParser = Object.assign(parser, { type: name });
|
|
18
|
+
const finalRenderer = Object.assign(renderer, { type: name });
|
|
19
|
+
return {
|
|
20
|
+
name,
|
|
21
|
+
type,
|
|
22
|
+
tokenizer: finalTokenizer,
|
|
23
|
+
parser: finalParser,
|
|
24
|
+
renderer: finalRenderer
|
|
25
|
+
};
|
|
26
|
+
}
|
package/dist/types/token.d.ts
CHANGED
|
@@ -114,7 +114,7 @@ export interface Token {
|
|
|
114
114
|
* @property emit - A function handle tokenizing input to `Token`.
|
|
115
115
|
*/
|
|
116
116
|
export interface TokenizerStrategy {
|
|
117
|
-
|
|
117
|
+
type: string;
|
|
118
118
|
/**
|
|
119
119
|
* Checks if the current cursor position in the Lexer matches this syntax
|
|
120
120
|
* @param lex The current `ILexer` instance providing access to the input string and cursor.
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.handleEndList = handleEndList;
|
|
7
4
|
exports.handleHtmlBlock = handleHtmlBlock;
|
|
@@ -12,7 +9,7 @@ exports.handleStartList = handleStartList;
|
|
|
12
9
|
exports.handleTable = handleTable;
|
|
13
10
|
exports.handleTaskItem = handleTaskItem;
|
|
14
11
|
exports.handleTextBlock = handleTextBlock;
|
|
15
|
-
const lexer_1 =
|
|
12
|
+
const lexer_1 = require("../core/lexer");
|
|
16
13
|
function handleTextBlock(lex) {
|
|
17
14
|
const currentChar = lex.peek();
|
|
18
15
|
if (currentChar === null)
|
|
@@ -106,7 +103,7 @@ function handleEndList(lex) {
|
|
|
106
103
|
//Table utilities
|
|
107
104
|
function handleTable(lex) {
|
|
108
105
|
const tokenizeResult = [];
|
|
109
|
-
const handler = new lexer_1.
|
|
106
|
+
const handler = new lexer_1.Lexer("");
|
|
110
107
|
const header = lex.readUntil("\n", true);
|
|
111
108
|
const headerDetails = header.trim().replace(/^ *\|/, "").replace(/\| *$/, "").split("|");
|
|
112
109
|
const align = lex.readUntil("\n", true);
|