simple-customize-markdown-converter 1.0.1 → 1.0.3
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 +12 -5
- package/dist/lexer.d.ts +14 -0
- package/dist/lexer.js +158 -10
- package/dist/parser.d.ts +7 -0
- package/dist/parser.js +136 -6
- package/dist/renderer.js +16 -3
- package/dist/types/node.d.ts +38 -2
- package/dist/types/token.d.ts +38 -4
- package/package.json +40 -40
package/README.md
CHANGED
|
@@ -3,12 +3,19 @@ This simple library help you convert Markdown to HTML and customize it.
|
|
|
3
3
|
|
|
4
4
|
## Feature
|
|
5
5
|
Currently, this lib only supports:
|
|
6
|
-
- Headings (
|
|
6
|
+
- Headings (`#, ##, …`)
|
|
7
7
|
- Paragraphs
|
|
8
|
-
- Bold (
|
|
9
|
-
- Italic (
|
|
10
|
-
-
|
|
11
|
-
-
|
|
8
|
+
- Bold (`\*\*text\*\*`)
|
|
9
|
+
- Italic (`\*text\* or \_text\_`)
|
|
10
|
+
- Strikethrough (`\~\~text\~\~`)
|
|
11
|
+
- Inline code (`\`code\``)
|
|
12
|
+
- Code blocks (`\`\`\`lang ... \`\`\``)
|
|
13
|
+
- Quote (`> text`)
|
|
14
|
+
- List (`- Item 1,...`)
|
|
15
|
+
- Tasklist (`- [ ], - \[x\]`)
|
|
16
|
+
- Link (`\[link\]\(url\)`)
|
|
17
|
+
- Image (`\[alt\]\(url\)`)
|
|
18
|
+
- Horizontal line (`---` or `***` or `___`)
|
|
12
19
|
|
|
13
20
|
And customizable renderer for all elements
|
|
14
21
|
|
package/dist/lexer.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export default class Lexer {
|
|
|
3
3
|
input: string;
|
|
4
4
|
pos: number;
|
|
5
5
|
listToken: Token[];
|
|
6
|
+
listLevelFlag: number;
|
|
6
7
|
constructor(input: string);
|
|
7
8
|
/**
|
|
8
9
|
* Tokenize the markdown into a list of tokens.
|
|
@@ -19,5 +20,18 @@ export default class Lexer {
|
|
|
19
20
|
private handleTextBlock;
|
|
20
21
|
private handleItalic;
|
|
21
22
|
private handleBold;
|
|
23
|
+
private handleStrikethrough;
|
|
22
24
|
private handleInlineBlock;
|
|
25
|
+
private handleQuoteBlock;
|
|
26
|
+
private handleList;
|
|
27
|
+
private handleStartList;
|
|
28
|
+
private handleListItem;
|
|
29
|
+
private handleTaskItem;
|
|
30
|
+
private handleEndList;
|
|
31
|
+
private handleLink;
|
|
32
|
+
private handleImage;
|
|
33
|
+
private handleHorizontalLine;
|
|
34
|
+
private readUntil;
|
|
35
|
+
private peekUntil;
|
|
36
|
+
private isStartOfLine;
|
|
23
37
|
}
|
package/dist/lexer.js
CHANGED
|
@@ -4,6 +4,8 @@ class Lexer {
|
|
|
4
4
|
constructor(input) {
|
|
5
5
|
this.pos = 0;
|
|
6
6
|
this.listToken = [];
|
|
7
|
+
// Flag for handle special syntax
|
|
8
|
+
this.listLevelFlag = 0;
|
|
7
9
|
this.input = input;
|
|
8
10
|
}
|
|
9
11
|
/**
|
|
@@ -12,11 +14,52 @@ class Lexer {
|
|
|
12
14
|
*/
|
|
13
15
|
tokenize() {
|
|
14
16
|
const TOKEN_HANDLER = [
|
|
17
|
+
//Handle escape character first
|
|
18
|
+
{
|
|
19
|
+
match: (lex) => lex.peek() === "\\" && lex.peek(1) !== undefined,
|
|
20
|
+
emit: (lex) => {
|
|
21
|
+
lex.next(1);
|
|
22
|
+
lex.handleTextBlock();
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
//Regex: if line started with at least 3 characters: -, *, _
|
|
27
|
+
match: (lex) => /^([-*_])\1{2,}$/.test(lex.peekUntil("\n").trim()) && this.getLastToken()?.type === "NewLine",
|
|
28
|
+
emit: (lex) => lex.handleHorizontalLine()
|
|
29
|
+
},
|
|
15
30
|
{ match: (lex) => lex.startsWith("```"), emit: (lex) => lex.handleCodeBlock() },
|
|
16
31
|
{ match: (lex) => lex.startsWith("**"), emit: (lex) => lex.handleBold() },
|
|
32
|
+
{ match: (lex) => lex.startsWith("~~"), emit: (lex) => lex.handleStrikethrough() },
|
|
33
|
+
//For List
|
|
34
|
+
{
|
|
35
|
+
match: (lex) => lex.isStartOfLine() && /^(\s*)([-*+]) \[( |x|X)\] /.test(lex.peekUntil("\n")),
|
|
36
|
+
emit: (lex) => lex.handleList(false, true)
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
//Regex: if line started with zero or more spaces, then have - or + or * + 1 space
|
|
40
|
+
match: (lex) => lex.isStartOfLine() && /^(\s*)([-*+]) /.test(lex.peekUntil("\n")),
|
|
41
|
+
emit: (lex) => lex.handleList(false, false)
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
//Regex: if line started with zero or more spaces, then have number. character + 1 space
|
|
45
|
+
match: (lex) => lex.isStartOfLine() && /^(\s*)(\d+)\. /.test(lex.peekUntil("\n")),
|
|
46
|
+
emit: (lex) => lex.handleList(true, false)
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
match: (lex) => lex.listLevelFlag > 0 && lex.isStartOfLine() && !/^(\s*)([-+*]|\d+\.) /.test(lex.peekUntil("\n")),
|
|
50
|
+
emit: (lex) => {
|
|
51
|
+
while (lex.listLevelFlag > 0) {
|
|
52
|
+
lex.handleEndList();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
//For common syntax
|
|
17
57
|
{ match: (lex) => lex.peek() === "`", emit: (lex) => lex.handleInlineBlock() },
|
|
18
58
|
{ match: (lex) => lex.peek() === "#", emit: (lex) => lex.handleHeader() },
|
|
19
59
|
{ match: (lex) => lex.peek() === "*" || lex.peek() === "_", emit: (lex) => lex.handleItalic() },
|
|
60
|
+
{ match: (lex) => lex.peek() === ">", emit: (lex) => lex.handleQuoteBlock() },
|
|
61
|
+
{ match: (lex) => lex.peek() === "[", emit: (lex) => lex.handleLink() },
|
|
62
|
+
{ match: (lex) => lex.peek() === "!" && lex.peek(1) === "[", emit: (lex) => lex.handleImage() },
|
|
20
63
|
{ match: (lex) => lex.peek() === "\n", emit: (lex) => lex.listToken.push({ type: "NewLine" }) },
|
|
21
64
|
];
|
|
22
65
|
while (!this.isEndOfFile()) {
|
|
@@ -33,6 +76,9 @@ class Lexer {
|
|
|
33
76
|
}
|
|
34
77
|
this.next();
|
|
35
78
|
}
|
|
79
|
+
while (this.listLevelFlag > 0) {
|
|
80
|
+
this.handleEndList();
|
|
81
|
+
}
|
|
36
82
|
this.listToken.push({ type: "EOF" });
|
|
37
83
|
return this.listToken;
|
|
38
84
|
}
|
|
@@ -56,18 +102,16 @@ class Lexer {
|
|
|
56
102
|
return this.listToken[this.listToken.length - 1];
|
|
57
103
|
}
|
|
58
104
|
handleHeader() {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
else if (lastToken.type === "Header") {
|
|
64
|
-
lastToken.level++;
|
|
105
|
+
let level = 0;
|
|
106
|
+
while (this.peek() === "#") {
|
|
107
|
+
level++;
|
|
108
|
+
this.next();
|
|
65
109
|
}
|
|
66
|
-
this.next();
|
|
67
110
|
if (this.peek() === " ") {
|
|
68
111
|
this.next();
|
|
69
112
|
this.pos--;
|
|
70
113
|
}
|
|
114
|
+
this.listToken.push({ type: "Header", level });
|
|
71
115
|
}
|
|
72
116
|
handleCodeBlock() {
|
|
73
117
|
let lang = "";
|
|
@@ -83,7 +127,7 @@ class Lexer {
|
|
|
83
127
|
this.next();
|
|
84
128
|
}
|
|
85
129
|
this.next(2); //Skip close block (due to next() after each tokenize iteration)
|
|
86
|
-
this.listToken.push({ "type": "CodeBlock", lang: lang.trim(), content: content });
|
|
130
|
+
this.listToken.push({ "type": "CodeBlock", lang: lang.trim(), content: content.trimEnd() });
|
|
87
131
|
}
|
|
88
132
|
handleTextBlock() {
|
|
89
133
|
const currentChar = this.peek();
|
|
@@ -100,7 +144,11 @@ class Lexer {
|
|
|
100
144
|
}
|
|
101
145
|
handleBold() {
|
|
102
146
|
this.listToken.push({ type: "Bold" });
|
|
103
|
-
this.next(); //Skip *
|
|
147
|
+
this.next(); //Skip remain *
|
|
148
|
+
}
|
|
149
|
+
handleStrikethrough() {
|
|
150
|
+
this.listToken.push({ type: "Strikethrough" });
|
|
151
|
+
this.next(); //Skip remain ~
|
|
104
152
|
}
|
|
105
153
|
handleInlineBlock() {
|
|
106
154
|
let content = "";
|
|
@@ -109,8 +157,108 @@ class Lexer {
|
|
|
109
157
|
content += this.peek();
|
|
110
158
|
this.next();
|
|
111
159
|
}
|
|
112
|
-
// this.next() //Skip close block
|
|
113
160
|
this.listToken.push({ "type": "InlineCode", content: content });
|
|
114
161
|
}
|
|
162
|
+
handleQuoteBlock() {
|
|
163
|
+
this.listToken.push({ type: "Quote" });
|
|
164
|
+
}
|
|
165
|
+
handleList(isOrdered, isTask) {
|
|
166
|
+
const line = this.peekUntil("\n");
|
|
167
|
+
if (isTask) {
|
|
168
|
+
const m = line.match(/^(\s*)([-*+]) \[( |x|X)\] (.*)$/);
|
|
169
|
+
const indent = Math.floor(m[1].length / 2) + 1;
|
|
170
|
+
while (this.listLevelFlag < indent)
|
|
171
|
+
this.handleStartList(false);
|
|
172
|
+
while (this.listLevelFlag > indent)
|
|
173
|
+
this.handleEndList();
|
|
174
|
+
this.next(m[1].length + 4);
|
|
175
|
+
this.handleTaskItem(m[3].toLowerCase() === "x");
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
//Regex: line started with: Group 1: zero or more spaces, group 2: (- or + or * + 1 space) or (number with . character), group 3: everything else in line
|
|
179
|
+
const m = isOrdered ? line.match(/^(\s*)(\d+)\. (.*)$/) : line.match(/^(\s*)([-*+]) (.*)$/);
|
|
180
|
+
const indent = Math.floor(m[1].length / 2) + 1; //m[1] to get the spaces in group 1
|
|
181
|
+
while (this.listLevelFlag < indent)
|
|
182
|
+
this.handleStartList(isOrdered);
|
|
183
|
+
while (this.listLevelFlag > indent)
|
|
184
|
+
this.handleEndList();
|
|
185
|
+
this.next(m[1].length + (isOrdered ? 1 : 0)); //+1 due to marker have 2 characters (e.g: 1.) instead 1 like unordered list
|
|
186
|
+
this.handleListItem();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
handleStartList(isOrder) {
|
|
190
|
+
this.listLevelFlag++;
|
|
191
|
+
this.listToken.push({ type: "ListStart", level: this.listLevelFlag, ordered: isOrder });
|
|
192
|
+
}
|
|
193
|
+
handleListItem() {
|
|
194
|
+
this.next(); // Skip space between - and text
|
|
195
|
+
this.listToken.push({ type: "ListItem" });
|
|
196
|
+
}
|
|
197
|
+
handleTaskItem(isChecked) {
|
|
198
|
+
this.next(); // Skip space between last ] and text
|
|
199
|
+
this.listToken.push({ type: "TaskItem", checked: isChecked });
|
|
200
|
+
}
|
|
201
|
+
handleEndList() {
|
|
202
|
+
this.listLevelFlag === 0 ? 0 : this.listLevelFlag--;
|
|
203
|
+
this.listToken.push({ type: "ListEnd" });
|
|
204
|
+
}
|
|
205
|
+
handleLink() {
|
|
206
|
+
this.next(); //Skip [
|
|
207
|
+
const text = this.readUntil("]");
|
|
208
|
+
this.next(); //Skip ]
|
|
209
|
+
if (this.peek() === "(") {
|
|
210
|
+
this.next(); //Skip (
|
|
211
|
+
const url = this.readUntil(")");
|
|
212
|
+
//Don't skip ) due to auto skip on while loop
|
|
213
|
+
this.listToken.push({ type: "Link", text: text, href: url });
|
|
214
|
+
}
|
|
215
|
+
else
|
|
216
|
+
this.listToken.push({ type: "Text", value: `[${text}]` });
|
|
217
|
+
}
|
|
218
|
+
handleImage() {
|
|
219
|
+
this.next(); //Skip !
|
|
220
|
+
if (this.peek() !== "[")
|
|
221
|
+
return;
|
|
222
|
+
this.next(); //Skip [
|
|
223
|
+
const alt = this.readUntil("]");
|
|
224
|
+
this.next(); //Skip ]
|
|
225
|
+
if (this.peek() === "(") {
|
|
226
|
+
this.next(); //Skip (
|
|
227
|
+
const src = this.readUntil(")");
|
|
228
|
+
this.next(); //Skip )
|
|
229
|
+
this.listToken.push({ type: "Image", alt: alt, src: src });
|
|
230
|
+
}
|
|
231
|
+
else
|
|
232
|
+
this.listToken.push({ type: "Text", value: `![${alt}]` });
|
|
233
|
+
}
|
|
234
|
+
handleHorizontalLine() {
|
|
235
|
+
this.next(2); //Skip two first characters, remain will be skiped after loop
|
|
236
|
+
this.listToken.push({ type: "HorizontalLine" });
|
|
237
|
+
}
|
|
238
|
+
//Utilities function
|
|
239
|
+
readUntil(char) {
|
|
240
|
+
let result = "";
|
|
241
|
+
while (this.peek() !== char) {
|
|
242
|
+
result += this.peek();
|
|
243
|
+
this.next();
|
|
244
|
+
}
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
peekUntil(char) {
|
|
248
|
+
let result = "";
|
|
249
|
+
let i = 0;
|
|
250
|
+
while (true) {
|
|
251
|
+
const current = this.peek(i++);
|
|
252
|
+
if (current == null)
|
|
253
|
+
break;
|
|
254
|
+
if (current == char)
|
|
255
|
+
break;
|
|
256
|
+
result += current;
|
|
257
|
+
}
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
isStartOfLine() {
|
|
261
|
+
return this.pos === 0 || this.peek(-1) === "\n";
|
|
262
|
+
}
|
|
115
263
|
}
|
|
116
264
|
exports.default = Lexer;
|
package/dist/parser.d.ts
CHANGED
|
@@ -18,6 +18,13 @@ export declare class Parser {
|
|
|
18
18
|
private parseHeader;
|
|
19
19
|
private parseBold;
|
|
20
20
|
private parseItalic;
|
|
21
|
+
private parseStrikethrough;
|
|
21
22
|
private parseInlineCode;
|
|
23
|
+
private parseQuote;
|
|
24
|
+
private parseList;
|
|
25
|
+
private parseListItem;
|
|
26
|
+
private parseLink;
|
|
27
|
+
private parseImage;
|
|
28
|
+
private parseHorizontalLine;
|
|
22
29
|
private parseInlineUntil;
|
|
23
30
|
}
|
package/dist/parser.js
CHANGED
|
@@ -37,12 +37,26 @@ class Parser {
|
|
|
37
37
|
listNode.push(this.parseHeader());
|
|
38
38
|
break;
|
|
39
39
|
}
|
|
40
|
-
case "CodeBlock":
|
|
41
|
-
|
|
42
|
-
listNode.push(this.parseCodeBlock());
|
|
43
|
-
this.next();
|
|
44
|
-
}
|
|
40
|
+
case "CodeBlock": {
|
|
41
|
+
listNode.push(this.parseCodeBlock());
|
|
45
42
|
break;
|
|
43
|
+
}
|
|
44
|
+
case "Quote": {
|
|
45
|
+
listNode.push(this.parseQuote());
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case "Image": {
|
|
49
|
+
listNode.push(this.parseImage());
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
case "HorizontalLine": {
|
|
53
|
+
listNode.push(this.parseHorizontalLine());
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case "ListStart": {
|
|
57
|
+
listNode.push(this.parseList());
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
46
60
|
case "NewLine": {
|
|
47
61
|
this.next(); // skip
|
|
48
62
|
break;
|
|
@@ -60,6 +74,7 @@ class Parser {
|
|
|
60
74
|
}
|
|
61
75
|
parseCodeBlock() {
|
|
62
76
|
const tok = this.peek();
|
|
77
|
+
this.next();
|
|
63
78
|
return {
|
|
64
79
|
type: "CodeBlock",
|
|
65
80
|
lang: tok?.type === "CodeBlock" ? tok.lang : "",
|
|
@@ -83,6 +98,10 @@ class Parser {
|
|
|
83
98
|
this.next(); // skip marker
|
|
84
99
|
return { type: "Italic", children: this.parseInlineUntil("Italic") };
|
|
85
100
|
}
|
|
101
|
+
parseStrikethrough() {
|
|
102
|
+
this.next(); // skip marker
|
|
103
|
+
return { type: "Strikethrough", children: this.parseInlineUntil("Strikethrough") };
|
|
104
|
+
}
|
|
86
105
|
parseInlineCode() {
|
|
87
106
|
const tok = this.peek();
|
|
88
107
|
this.next();
|
|
@@ -91,12 +110,113 @@ class Parser {
|
|
|
91
110
|
content: tok?.type === "InlineCode" ? tok.content : ""
|
|
92
111
|
};
|
|
93
112
|
}
|
|
113
|
+
parseQuote() {
|
|
114
|
+
this.next(); //skip marker
|
|
115
|
+
return { type: "Quote", children: [{ type: "Paragraph", children: this.parseInlineUntil("NewLine") }] };
|
|
116
|
+
}
|
|
117
|
+
parseList() {
|
|
118
|
+
const tok = this.peek();
|
|
119
|
+
if (tok?.type === "ListStart") {
|
|
120
|
+
this.next(); //skip marker
|
|
121
|
+
const result = {
|
|
122
|
+
type: "List",
|
|
123
|
+
level: tok.level,
|
|
124
|
+
ordered: tok.ordered,
|
|
125
|
+
children: [],
|
|
126
|
+
};
|
|
127
|
+
let nextToken = this.peek();
|
|
128
|
+
while (!this.isEnd()) {
|
|
129
|
+
if (nextToken?.type === "ListItem" || nextToken?.type === "TaskItem") {
|
|
130
|
+
result.children.push(this.parseListItem());
|
|
131
|
+
nextToken = this.peek();
|
|
132
|
+
}
|
|
133
|
+
else if (nextToken?.type === "ListEnd") {
|
|
134
|
+
this.next();
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
else
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
//Temp return
|
|
143
|
+
return {
|
|
144
|
+
type: "Text",
|
|
145
|
+
value: ""
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
parseListItem() {
|
|
149
|
+
const currentToken = this.peek();
|
|
150
|
+
this.next(); // skip marker
|
|
151
|
+
const children = [];
|
|
152
|
+
while (!this.isEnd()) {
|
|
153
|
+
const tok = this.peek();
|
|
154
|
+
if (!tok)
|
|
155
|
+
break;
|
|
156
|
+
if (tok.type === "NewLine") {
|
|
157
|
+
this.next();
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (tok.type === "ListStart") {
|
|
161
|
+
children.push(this.parseList());
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (["ListItem", "TaskItem", "ListEnd"].includes(tok.type)) {
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
children.push({
|
|
168
|
+
type: "Paragraph",
|
|
169
|
+
children: this.parseInlineUntil("NewLine")
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return currentToken?.type === "TaskItem" ? {
|
|
173
|
+
type: "TaskItem",
|
|
174
|
+
checked: currentToken.type === "TaskItem" ? currentToken.checked : false,
|
|
175
|
+
children: children
|
|
176
|
+
} : {
|
|
177
|
+
type: "ListItem",
|
|
178
|
+
children: children
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
parseLink() {
|
|
182
|
+
const tok = this.peek();
|
|
183
|
+
this.next();
|
|
184
|
+
if (tok?.type === "Link") {
|
|
185
|
+
return {
|
|
186
|
+
type: "Link",
|
|
187
|
+
href: tok.href,
|
|
188
|
+
text: tok.text
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return { type: "Link", href: "", text: "" };
|
|
192
|
+
}
|
|
193
|
+
parseImage() {
|
|
194
|
+
const tok = this.peek();
|
|
195
|
+
this.next();
|
|
196
|
+
if (tok?.type === "Image") {
|
|
197
|
+
return {
|
|
198
|
+
type: "Image",
|
|
199
|
+
src: tok.src,
|
|
200
|
+
alt: tok.alt
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
else
|
|
204
|
+
return { type: "Image", src: "", alt: "" };
|
|
205
|
+
}
|
|
206
|
+
parseHorizontalLine() {
|
|
207
|
+
const tok = this.peek();
|
|
208
|
+
this.next(); // skip marker
|
|
209
|
+
return { type: "HorizontalLine" };
|
|
210
|
+
}
|
|
94
211
|
parseInlineUntil(stopType) {
|
|
212
|
+
const stop = Array.isArray(stopType) ? stopType : [stopType];
|
|
95
213
|
const listNode = [];
|
|
96
|
-
while (!this.isEnd()
|
|
214
|
+
while (!this.isEnd()) {
|
|
97
215
|
const currentNode = this.peek();
|
|
98
216
|
if (!currentNode)
|
|
99
217
|
break;
|
|
218
|
+
if (stop.includes(currentNode.type))
|
|
219
|
+
break;
|
|
100
220
|
switch (currentNode.type) {
|
|
101
221
|
case "Bold": {
|
|
102
222
|
listNode.push(this.parseBold());
|
|
@@ -106,12 +226,22 @@ class Parser {
|
|
|
106
226
|
listNode.push(this.parseItalic());
|
|
107
227
|
break;
|
|
108
228
|
}
|
|
229
|
+
case "Strikethrough": {
|
|
230
|
+
listNode.push(this.parseStrikethrough());
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
109
233
|
case "InlineCode": {
|
|
110
234
|
listNode.push(this.parseInlineCode());
|
|
111
235
|
break;
|
|
112
236
|
}
|
|
113
237
|
case "Text": {
|
|
114
238
|
listNode.push({ type: "Text", value: currentNode.value });
|
|
239
|
+
this.next();
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
case "Link": {
|
|
243
|
+
listNode.push(this.parseLink());
|
|
244
|
+
break;
|
|
115
245
|
}
|
|
116
246
|
default: this.next();
|
|
117
247
|
}
|
package/dist/renderer.js
CHANGED
|
@@ -19,14 +19,27 @@ class Renderer {
|
|
|
19
19
|
}
|
|
20
20
|
handleRender(type) {
|
|
21
21
|
const defaultRender = {
|
|
22
|
+
//Base structural nodes
|
|
22
23
|
Document: (_node, children) => children.join(""),
|
|
23
24
|
Paragraph: (_node, children) => `<p>${children.join("")}</p>`,
|
|
24
|
-
|
|
25
|
-
InlineCode: (node) => `<code>${this.escapeHtml(node.content)}</code>`,
|
|
25
|
+
//Container nodes
|
|
26
26
|
CodeBlock: (node) => `<pre><code class="lang-${node.lang}">${this.escapeHtml(node.content)}</code></pre>`,
|
|
27
|
+
Header: (node, children) => `<h${node.level}${node.level <= 2 ? ' style="border-bottom: 1px solid #d1d9e0b3"' : ''}>${children.join("")}</h${node.level}>`,
|
|
28
|
+
Quote: (_node, children) => `<blockquote>${children.join("")}</blockquote>`,
|
|
29
|
+
List: (node, children) => node.ordered ? `<ol>${children.join("")}</ol>` : `<ul>${children.join("")}</ul>`,
|
|
30
|
+
ListItem: (_node, children) => `<li>${children.join("")}</li>`,
|
|
31
|
+
TaskItem: (node, children) => `<li><input type="checkbox" disabled ${node.checked ? "checked" : ""}>${children.join("")}</li>`,
|
|
32
|
+
//Styling nodes
|
|
27
33
|
Bold: (_node, children) => `<strong>${children.join("")}</strong>`,
|
|
28
34
|
Italic: (_node, children) => `<em>${children.join("")}</em>`,
|
|
29
|
-
|
|
35
|
+
Strikethrough: (_node, children) => `<s>${children.join("")}</s>`,
|
|
36
|
+
InlineCode: (node) => `<code>${this.escapeHtml(node.content)}</code>`,
|
|
37
|
+
//Media nodes
|
|
38
|
+
Link: (node) => `<a href="${node.href}">${node.text}</a>`,
|
|
39
|
+
Image: (node) => `<img src="${node.src}" alt="${node.alt}"/>`,
|
|
40
|
+
//Leaf nodes
|
|
41
|
+
HorizontalLine: (_node) => `<hr>`,
|
|
42
|
+
Text: (node) => node.value,
|
|
30
43
|
};
|
|
31
44
|
return this.option.elements?.[type] ?? defaultRender[type];
|
|
32
45
|
}
|
package/dist/types/node.d.ts
CHANGED
|
@@ -10,8 +10,16 @@
|
|
|
10
10
|
* - Header: A header with given `level` (1-6)
|
|
11
11
|
* - Bold: Bold text
|
|
12
12
|
* - Italic: Italic text
|
|
13
|
-
* -
|
|
14
|
-
* -
|
|
13
|
+
* - Strikethrough: Strilethrough text
|
|
14
|
+
* - InlineCode: Inline code snippet, with it's `content`
|
|
15
|
+
* - Quote: A quote block
|
|
16
|
+
* - CodeBlock: A code block, with it's `lang` and `content`
|
|
17
|
+
* - List: A list, with it's level and children
|
|
18
|
+
* - ListItem: An item of a list, with it's children
|
|
19
|
+
* - TaskItem: An item for tasklist, with it's checked state
|
|
20
|
+
* - Link: A link, with it's `text` and `href`
|
|
21
|
+
* - Image: An image, with it's `src` and `alt`
|
|
22
|
+
* - HorizontalLine: A horizontal line
|
|
15
23
|
* - Text: Raw text content.
|
|
16
24
|
*/
|
|
17
25
|
export type Node = {
|
|
@@ -30,6 +38,9 @@ export type Node = {
|
|
|
30
38
|
} | {
|
|
31
39
|
type: "Italic";
|
|
32
40
|
children: Node[];
|
|
41
|
+
} | {
|
|
42
|
+
type: "Strikethrough";
|
|
43
|
+
children: Node[];
|
|
33
44
|
} | {
|
|
34
45
|
type: "InlineCode";
|
|
35
46
|
content: string;
|
|
@@ -37,6 +48,31 @@ export type Node = {
|
|
|
37
48
|
type: "CodeBlock";
|
|
38
49
|
lang: string;
|
|
39
50
|
content: string;
|
|
51
|
+
} | {
|
|
52
|
+
type: "Quote";
|
|
53
|
+
children: Node[];
|
|
54
|
+
} | {
|
|
55
|
+
type: "List";
|
|
56
|
+
ordered: boolean;
|
|
57
|
+
level: number;
|
|
58
|
+
children: Node[];
|
|
59
|
+
} | {
|
|
60
|
+
type: "ListItem";
|
|
61
|
+
children: Node[];
|
|
62
|
+
} | {
|
|
63
|
+
type: "TaskItem";
|
|
64
|
+
checked: boolean;
|
|
65
|
+
children: Node[];
|
|
66
|
+
} | {
|
|
67
|
+
type: "Link";
|
|
68
|
+
href: string;
|
|
69
|
+
text: string;
|
|
70
|
+
} | {
|
|
71
|
+
type: "Image";
|
|
72
|
+
src: string;
|
|
73
|
+
alt: string;
|
|
74
|
+
} | {
|
|
75
|
+
type: "HorizontalLine";
|
|
40
76
|
} | {
|
|
41
77
|
type: "Text";
|
|
42
78
|
value: string;
|
package/dist/types/token.d.ts
CHANGED
|
@@ -8,10 +8,19 @@
|
|
|
8
8
|
* - Header: Markdown header (`#`), with a `level` (1–6).
|
|
9
9
|
* - CodeBlock: Fenced code block (` ``` `), with optional `lang` and its `content`.
|
|
10
10
|
* - NewLine: Line break (`\n`).
|
|
11
|
-
* - Text: Plain text content.
|
|
12
11
|
* - Bold: Bold marker (`**`).
|
|
13
12
|
* - Italic: Italic marker (`*` or `_`).
|
|
13
|
+
* - Strikethrough: Strikethrough marker (`~~`)
|
|
14
14
|
* - InlineCode: Inline code snippet (`` ` ``), with its `content`.
|
|
15
|
+
* - Quote: A quote block (`>`).
|
|
16
|
+
* - ListStart: Start a list
|
|
17
|
+
* - ListItem: A list's item (`* ` or `+ ` or `- ` or `number with dot`)
|
|
18
|
+
* - TaskItem: A task item in a list (`- [ ]` or `- [x]`)
|
|
19
|
+
* - ListEnd: End a list
|
|
20
|
+
* - Link: A link (`[text](url)`)
|
|
21
|
+
* - Image: An image (``)
|
|
22
|
+
* - HorizontalLine: A horizontal line (`---` or `___` or `***`)
|
|
23
|
+
* - Text: Plain text content.
|
|
15
24
|
* - EOF: A special token, this is the end of input.
|
|
16
25
|
*/
|
|
17
26
|
export type Token = {
|
|
@@ -23,16 +32,41 @@ export type Token = {
|
|
|
23
32
|
content: string;
|
|
24
33
|
} | {
|
|
25
34
|
type: "NewLine";
|
|
26
|
-
} | {
|
|
27
|
-
type: "Text";
|
|
28
|
-
value: string;
|
|
29
35
|
} | {
|
|
30
36
|
type: "Bold";
|
|
31
37
|
} | {
|
|
32
38
|
type: "Italic";
|
|
39
|
+
} | {
|
|
40
|
+
type: "Strikethrough";
|
|
33
41
|
} | {
|
|
34
42
|
type: "InlineCode";
|
|
35
43
|
content: string;
|
|
44
|
+
} | {
|
|
45
|
+
type: "Quote";
|
|
46
|
+
} | {
|
|
47
|
+
type: "ListStart";
|
|
48
|
+
ordered: boolean;
|
|
49
|
+
level: number;
|
|
50
|
+
} | {
|
|
51
|
+
type: "ListItem";
|
|
52
|
+
} | {
|
|
53
|
+
type: "TaskItem";
|
|
54
|
+
checked: boolean;
|
|
55
|
+
} | {
|
|
56
|
+
type: "ListEnd";
|
|
57
|
+
} | {
|
|
58
|
+
type: "Link";
|
|
59
|
+
text: string;
|
|
60
|
+
href: string;
|
|
61
|
+
} | {
|
|
62
|
+
type: "Image";
|
|
63
|
+
src: string;
|
|
64
|
+
alt: string;
|
|
65
|
+
} | {
|
|
66
|
+
type: "HorizontalLine";
|
|
67
|
+
} | {
|
|
68
|
+
type: "Text";
|
|
69
|
+
value: string;
|
|
36
70
|
} | {
|
|
37
71
|
type: "EOF";
|
|
38
72
|
};
|
package/package.json
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "simple-customize-markdown-converter",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Convert Markdown to your customize HTML",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"markdown",
|
|
7
|
-
"html",
|
|
8
|
-
"converter"
|
|
9
|
-
],
|
|
10
|
-
"author": "Regiko04",
|
|
11
|
-
"license": "MIT",
|
|
12
|
-
"main": "dist/index.js",
|
|
13
|
-
"module": "dist/index.js",
|
|
14
|
-
"types": "dist/index.d.ts",
|
|
15
|
-
"files": [
|
|
16
|
-
"dist",
|
|
17
|
-
"LICENSE",
|
|
18
|
-
"README.md"
|
|
19
|
-
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"test": "jest",
|
|
22
|
-
"build": "tsc",
|
|
23
|
-
"start": "ts-node src/index.ts"
|
|
24
|
-
},
|
|
25
|
-
"devDependencies": {
|
|
26
|
-
"@types/jest": "^30.0.0",
|
|
27
|
-
"@types/node": "^24.3.3",
|
|
28
|
-
"jest": "^30.1.3",
|
|
29
|
-
"ts-jest": "^29.4.1",
|
|
30
|
-
"ts-node": "^10.9.2",
|
|
31
|
-
"typescript": "^5.9.2"
|
|
32
|
-
},
|
|
33
|
-
"repository": {
|
|
34
|
-
"type": "git",
|
|
35
|
-
"url": "git+https://github.com/Riiichan04/simple-custom-markdown-converter.git"
|
|
36
|
-
},
|
|
37
|
-
"bugs": {
|
|
38
|
-
"url": "https://github.com/Riiichan04/simple-custom-markdown-converter/issues"
|
|
39
|
-
},
|
|
40
|
-
"homepage": "https://github.com/Riiichan04/simple-custom-markdown-converter#readme"
|
|
1
|
+
{
|
|
2
|
+
"name": "simple-customize-markdown-converter",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "Convert Markdown to your customize HTML",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"markdown",
|
|
7
|
+
"html",
|
|
8
|
+
"converter"
|
|
9
|
+
],
|
|
10
|
+
"author": "Regiko04",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"main": "dist/index.js",
|
|
13
|
+
"module": "dist/index.js",
|
|
14
|
+
"types": "dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "jest",
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"start": "ts-node src/index.ts"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/jest": "^30.0.0",
|
|
27
|
+
"@types/node": "^24.3.3",
|
|
28
|
+
"jest": "^30.1.3",
|
|
29
|
+
"ts-jest": "^29.4.1",
|
|
30
|
+
"ts-node": "^10.9.2",
|
|
31
|
+
"typescript": "^5.9.2"
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/Riiichan04/simple-custom-markdown-converter.git"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/Riiichan04/simple-custom-markdown-converter/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/Riiichan04/simple-custom-markdown-converter#readme"
|
|
41
41
|
}
|