simple-customize-markdown-converter 1.0.2 → 1.0.4

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 CHANGED
@@ -2,13 +2,21 @@
2
2
  This simple library help you convert Markdown to HTML and customize it.
3
3
 
4
4
  ## Feature
5
- Currently, this lib only supports:
6
- - Headings (#, ##, )
5
+ This library currently supports the most common Markdown syntaxes:
6
+ - Headings (`#, ##, …`)
7
7
  - Paragraphs
8
- - Bold (\*\*text\*\*)
9
- - Italic (\*text\* or \_text\_)
10
- - Inline code (\`code\`)
11
- - Code blocks (\`\`\`lang ... \`\`\`)
8
+ - Bold (`\*\*text\*\*`)
9
+ - Italic (`\*text\* or \_text\_`)
10
+ - Strikethrough (`\~\~text\~\~`)
11
+ - Inline code (`\`code\``)
12
+ - Code blocks (`\`\`\`lang ... \`\`\``)
13
+ - Quotes (`> text`)
14
+ - Lists (`- Item 1,...`)
15
+ - Tasklists (`- [ ], - \[x\]`)
16
+ - Links (`\[link\]\(url\)`)
17
+ - Images (`\[alt\]\(url\)`)
18
+ - Horizontal lines (`---` or `***` or `___`)
19
+ - Tables
12
20
 
13
21
  And customizable renderer for all elements
14
22
 
package/dist/lexer.d.ts CHANGED
@@ -3,25 +3,38 @@ export default class Lexer {
3
3
  input: string;
4
4
  pos: number;
5
5
  listToken: Token[];
6
+ listLevelFlag: number;
6
7
  constructor(input: string);
8
+ setInput(input: string): void;
7
9
  /**
8
10
  * Tokenize the markdown into a list of tokens.
11
+ * @param isEof - `True` when input is whole markdown, `False` if input is just a part of markdown.
9
12
  * @returns List of tokens
10
13
  */
11
- tokenize(): Token[];
14
+ tokenize(isEof?: boolean): Token[];
12
15
  private peek;
13
16
  private next;
14
17
  private startsWith;
15
18
  private isEndOfFile;
16
19
  private getLastToken;
20
+ private handleTable;
17
21
  private handleHeader;
18
22
  private handleCodeBlock;
19
23
  private handleTextBlock;
20
24
  private handleItalic;
21
25
  private handleBold;
26
+ private handleStrikethrough;
22
27
  private handleInlineBlock;
23
28
  private handleQuoteBlock;
29
+ private handleList;
30
+ private handleStartList;
31
+ private handleListItem;
32
+ private handleTaskItem;
33
+ private handleEndList;
24
34
  private handleLink;
25
35
  private handleImage;
36
+ private handleHorizontalLine;
26
37
  private readUntil;
38
+ private peekUntil;
39
+ private isStartOfLine;
27
40
  }
package/dist/lexer.js CHANGED
@@ -4,16 +4,66 @@ 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
  }
11
+ //Reset input and other attribute
12
+ setInput(input) {
13
+ this.input = input;
14
+ this.pos = 0;
15
+ this.listLevelFlag = 0;
16
+ this.listToken = [];
17
+ }
9
18
  /**
10
19
  * Tokenize the markdown into a list of tokens.
20
+ * @param isEof - `True` when input is whole markdown, `False` if input is just a part of markdown.
11
21
  * @returns List of tokens
12
22
  */
13
- tokenize() {
23
+ tokenize(isEof = true) {
14
24
  const TOKEN_HANDLER = [
25
+ //Handle escape character first
26
+ {
27
+ match: (lex) => lex.peek() === "\\" && lex.peek(1) !== undefined,
28
+ emit: (lex) => {
29
+ lex.next(1);
30
+ lex.handleTextBlock();
31
+ }
32
+ },
33
+ {
34
+ //Regex: if line started with at least 3 characters: -, *, _
35
+ match: (lex) => /^([-*_])\1{2,}$/.test(lex.peekUntil("\n").trim()) && this.getLastToken()?.type === "NewLine",
36
+ emit: (lex) => lex.handleHorizontalLine()
37
+ },
15
38
  { match: (lex) => lex.startsWith("```"), emit: (lex) => lex.handleCodeBlock() },
16
39
  { match: (lex) => lex.startsWith("**"), emit: (lex) => lex.handleBold() },
40
+ { match: (lex) => lex.startsWith("~~"), emit: (lex) => lex.handleStrikethrough() },
41
+ //For List
42
+ {
43
+ match: (lex) => lex.isStartOfLine() && /^(\s*)([-*+]) \[( |x|X)\] /.test(lex.peekUntil("\n")),
44
+ emit: (lex) => lex.handleList(false, true)
45
+ },
46
+ {
47
+ //Regex: if line started with zero or more spaces, then have - or + or * + 1 space
48
+ match: (lex) => lex.isStartOfLine() && /^(\s*)([-*+]) /.test(lex.peekUntil("\n")),
49
+ emit: (lex) => lex.handleList(false, false)
50
+ },
51
+ {
52
+ //Regex: if line started with zero or more spaces, then have number. character + 1 space
53
+ match: (lex) => lex.isStartOfLine() && /^(\s*)(\d+)\. /.test(lex.peekUntil("\n")),
54
+ emit: (lex) => lex.handleList(true, false)
55
+ },
56
+ {
57
+ match: (lex) => lex.listLevelFlag > 0 && lex.isStartOfLine() && !/^(\s*)([-+*]|\d+\.) /.test(lex.peekUntil("\n")),
58
+ emit: (lex) => {
59
+ while (lex.listLevelFlag > 0) {
60
+ lex.handleEndList();
61
+ }
62
+ }
63
+ },
64
+ //For table
65
+ { match: (lex) => lex.isStartOfLine() && /^\s*\|.*\|\s*$/.test(lex.peekUntil("\n")), emit: (lex) => lex.handleTable() },
66
+ //For common syntax
17
67
  { match: (lex) => lex.peek() === "`", emit: (lex) => lex.handleInlineBlock() },
18
68
  { match: (lex) => lex.peek() === "#", emit: (lex) => lex.handleHeader() },
19
69
  { match: (lex) => lex.peek() === "*" || lex.peek() === "_", emit: (lex) => lex.handleItalic() },
@@ -36,7 +86,11 @@ class Lexer {
36
86
  }
37
87
  this.next();
38
88
  }
39
- this.listToken.push({ type: "EOF" });
89
+ while (this.listLevelFlag > 0) {
90
+ this.handleEndList();
91
+ }
92
+ if (isEof)
93
+ this.listToken.push({ type: "EOF" });
40
94
  return this.listToken;
41
95
  }
42
96
  //Get current character with offset
@@ -58,6 +112,59 @@ class Lexer {
58
112
  getLastToken() {
59
113
  return this.listToken[this.listToken.length - 1];
60
114
  }
115
+ handleTable() {
116
+ const tokenizeResult = [];
117
+ const handler = new Lexer("");
118
+ const header = this.readUntil("\n", true);
119
+ const headerDetails = header.trim().replace(/^ *\|/, "").replace(/\| *$/, "").split("|");
120
+ const align = this.readUntil("\n", true);
121
+ const alignDetails = align.trim().replace(/^ *\|/, "").replace(/\| *$/, "").split("|");
122
+ if (alignDetails.length !== headerDetails.length || !alignDetails.every(c => /^:?-{3,}:?$/.test(c))) {
123
+ this.listToken.push({ type: "Text", value: `${header}\n${align}\n` });
124
+ return;
125
+ }
126
+ else {
127
+ //Handle alignment
128
+ const normalizeAlign = alignDetails.map(value => {
129
+ if (value.startsWith(":") && value.endsWith(":"))
130
+ return "center";
131
+ else if (value.endsWith(":"))
132
+ return "right";
133
+ else
134
+ return "left";
135
+ });
136
+ tokenizeResult.push({ type: "TableStart" });
137
+ //Handle header
138
+ tokenizeResult.push({ type: "RowStart", isHeader: true });
139
+ headerDetails.forEach((cell, index) => {
140
+ tokenizeResult.push({ type: "CellStart", align: normalizeAlign[index] ?? "left" });
141
+ handler.setInput(cell.trim());
142
+ tokenizeResult.push(...handler.tokenize(false));
143
+ tokenizeResult.push({ type: "CellEnd" });
144
+ });
145
+ tokenizeResult.push({ type: "RowEnd" });
146
+ //Handle body
147
+ while (!this.isEndOfFile()) {
148
+ const body = this.readUntil("\n", true);
149
+ if (!body)
150
+ break;
151
+ const line = body.trim();
152
+ if (!line.startsWith("|") || !line.endsWith("|"))
153
+ break; //End of table
154
+ const bodyDetail = body.trim().replace(/^ *\|/, "").replace(/\| *$/, "").split("|");
155
+ tokenizeResult.push({ type: "RowStart", isHeader: false });
156
+ bodyDetail.forEach((cell, index) => {
157
+ tokenizeResult.push({ type: "CellStart", align: normalizeAlign[index] ?? "left" });
158
+ handler.setInput(cell.trim());
159
+ tokenizeResult.push(...handler.tokenize(false));
160
+ tokenizeResult.push({ type: "CellEnd" });
161
+ });
162
+ tokenizeResult.push({ type: "RowEnd" });
163
+ }
164
+ tokenizeResult.push({ type: "TableEnd" });
165
+ this.listToken.push(...tokenizeResult);
166
+ }
167
+ }
61
168
  handleHeader() {
62
169
  let level = 0;
63
170
  while (this.peek() === "#") {
@@ -101,7 +208,11 @@ class Lexer {
101
208
  }
102
209
  handleBold() {
103
210
  this.listToken.push({ type: "Bold" });
104
- this.next(); //Skip *
211
+ this.next(); //Skip remain *
212
+ }
213
+ handleStrikethrough() {
214
+ this.listToken.push({ type: "Strikethrough" });
215
+ this.next(); //Skip remain ~
105
216
  }
106
217
  handleInlineBlock() {
107
218
  let content = "";
@@ -110,12 +221,51 @@ class Lexer {
110
221
  content += this.peek();
111
222
  this.next();
112
223
  }
113
- // this.next() //Skip close block
114
224
  this.listToken.push({ "type": "InlineCode", content: content });
115
225
  }
116
226
  handleQuoteBlock() {
117
227
  this.listToken.push({ type: "Quote" });
118
228
  }
229
+ handleList(isOrdered, isTask) {
230
+ const line = this.peekUntil("\n");
231
+ if (isTask) {
232
+ const m = line.match(/^(\s*)([-*+]) \[( |x|X)\] (.*)$/);
233
+ const indent = Math.floor(m[1].length / 2) + 1;
234
+ while (this.listLevelFlag < indent)
235
+ this.handleStartList(false);
236
+ while (this.listLevelFlag > indent)
237
+ this.handleEndList();
238
+ this.next(m[1].length + 4);
239
+ this.handleTaskItem(m[3].toLowerCase() === "x");
240
+ }
241
+ else {
242
+ //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
243
+ const m = isOrdered ? line.match(/^(\s*)(\d+)\. (.*)$/) : line.match(/^(\s*)([-*+]) (.*)$/);
244
+ const indent = Math.floor(m[1].length / 2) + 1; //m[1] to get the spaces in group 1
245
+ while (this.listLevelFlag < indent)
246
+ this.handleStartList(isOrdered);
247
+ while (this.listLevelFlag > indent)
248
+ this.handleEndList();
249
+ this.next(m[1].length + (isOrdered ? 1 : 0)); //+1 due to marker have 2 characters (e.g: 1.) instead 1 like unordered list
250
+ this.handleListItem();
251
+ }
252
+ }
253
+ handleStartList(isOrder) {
254
+ this.listLevelFlag++;
255
+ this.listToken.push({ type: "ListStart", level: this.listLevelFlag, ordered: isOrder });
256
+ }
257
+ handleListItem() {
258
+ this.next(); // Skip space between - and text
259
+ this.listToken.push({ type: "ListItem" });
260
+ }
261
+ handleTaskItem(isChecked) {
262
+ this.next(); // Skip space between last ] and text
263
+ this.listToken.push({ type: "TaskItem", checked: isChecked });
264
+ }
265
+ handleEndList() {
266
+ this.listLevelFlag === 0 ? 0 : this.listLevelFlag--;
267
+ this.listToken.push({ type: "ListEnd" });
268
+ }
119
269
  handleLink() {
120
270
  this.next(); //Skip [
121
271
  const text = this.readUntil("]");
@@ -145,13 +295,38 @@ class Lexer {
145
295
  else
146
296
  this.listToken.push({ type: "Text", value: `![${alt}]` });
147
297
  }
148
- readUntil(char) {
298
+ handleHorizontalLine() {
299
+ this.next(2); //Skip two first characters, remain will be skiped after loop
300
+ this.listToken.push({ type: "HorizontalLine" });
301
+ }
302
+ //Utilities function
303
+ readUntil(char, isConsumeChar = false) {
149
304
  let result = "";
150
305
  while (this.peek() !== char) {
151
306
  result += this.peek();
152
307
  this.next();
308
+ if (this.isEndOfFile())
309
+ break;
310
+ }
311
+ if (isConsumeChar)
312
+ this.next(char.length); //Make cursor skip the char
313
+ return result;
314
+ }
315
+ peekUntil(char) {
316
+ let result = "";
317
+ let i = 0;
318
+ while (true) {
319
+ const current = this.peek(i++);
320
+ if (current == null)
321
+ break;
322
+ if (current == char)
323
+ break;
324
+ result += current;
153
325
  }
154
326
  return result;
155
327
  }
328
+ isStartOfLine() {
329
+ return this.pos === 0 || this.peek(-1) === "\n";
330
+ }
156
331
  }
157
332
  exports.default = Lexer;
package/dist/parser.d.ts CHANGED
@@ -18,9 +18,14 @@ export declare class Parser {
18
18
  private parseHeader;
19
19
  private parseBold;
20
20
  private parseItalic;
21
+ private parseStrikethrough;
21
22
  private parseInlineCode;
22
23
  private parseQuote;
24
+ private parseList;
25
+ private parseListItem;
23
26
  private parseLink;
24
27
  private parseImage;
28
+ private parseTable;
29
+ private parseHorizontalLine;
25
30
  private parseInlineUntil;
26
31
  }
package/dist/parser.js CHANGED
@@ -49,6 +49,18 @@ class Parser {
49
49
  listNode.push(this.parseImage());
50
50
  break;
51
51
  }
52
+ case "HorizontalLine": {
53
+ listNode.push(this.parseHorizontalLine());
54
+ break;
55
+ }
56
+ case "ListStart": {
57
+ listNode.push(this.parseList());
58
+ break;
59
+ }
60
+ case "TableStart": {
61
+ listNode.push(this.parseTable());
62
+ break;
63
+ }
52
64
  case "NewLine": {
53
65
  this.next(); // skip
54
66
  break;
@@ -90,6 +102,10 @@ class Parser {
90
102
  this.next(); // skip marker
91
103
  return { type: "Italic", children: this.parseInlineUntil("Italic") };
92
104
  }
105
+ parseStrikethrough() {
106
+ this.next(); // skip marker
107
+ return { type: "Strikethrough", children: this.parseInlineUntil("Strikethrough") };
108
+ }
93
109
  parseInlineCode() {
94
110
  const tok = this.peek();
95
111
  this.next();
@@ -102,6 +118,70 @@ class Parser {
102
118
  this.next(); //skip marker
103
119
  return { type: "Quote", children: [{ type: "Paragraph", children: this.parseInlineUntil("NewLine") }] };
104
120
  }
121
+ parseList() {
122
+ const tok = this.peek();
123
+ if (tok?.type === "ListStart") {
124
+ this.next(); //skip marker
125
+ const result = {
126
+ type: "List",
127
+ level: tok.level,
128
+ ordered: tok.ordered,
129
+ children: [],
130
+ };
131
+ let nextToken = this.peek();
132
+ while (!this.isEnd()) {
133
+ if (nextToken?.type === "ListItem" || nextToken?.type === "TaskItem") {
134
+ result.children.push(this.parseListItem());
135
+ nextToken = this.peek();
136
+ }
137
+ else if (nextToken?.type === "ListEnd") {
138
+ this.next();
139
+ break;
140
+ }
141
+ else
142
+ break;
143
+ }
144
+ return result;
145
+ }
146
+ //Temp return
147
+ return {
148
+ type: "Text",
149
+ value: ""
150
+ };
151
+ }
152
+ parseListItem() {
153
+ const currentToken = this.peek();
154
+ this.next(); // skip marker
155
+ const children = [];
156
+ while (!this.isEnd()) {
157
+ const tok = this.peek();
158
+ if (!tok)
159
+ break;
160
+ if (tok.type === "NewLine") {
161
+ this.next();
162
+ continue;
163
+ }
164
+ if (tok.type === "ListStart") {
165
+ children.push(this.parseList());
166
+ continue;
167
+ }
168
+ if (["ListItem", "TaskItem", "ListEnd"].includes(tok.type)) {
169
+ break;
170
+ }
171
+ children.push({
172
+ type: "Paragraph",
173
+ children: this.parseInlineUntil("NewLine")
174
+ });
175
+ }
176
+ return currentToken?.type === "TaskItem" ? {
177
+ type: "TaskItem",
178
+ checked: currentToken.type === "TaskItem" ? currentToken.checked : false,
179
+ children: children
180
+ } : {
181
+ type: "ListItem",
182
+ children: children
183
+ };
184
+ }
105
185
  parseLink() {
106
186
  const tok = this.peek();
107
187
  this.next();
@@ -127,12 +207,60 @@ class Parser {
127
207
  else
128
208
  return { type: "Image", src: "", alt: "" };
129
209
  }
210
+ parseTable() {
211
+ this.next(); // skip TableStart token
212
+ const parseRow = () => {
213
+ const rowStartToken = this.peek();
214
+ if (rowStartToken?.type !== "RowStart")
215
+ return { isHeader: false, cells: [] };
216
+ this.next(); // skip RowStart token
217
+ const cells = [];
218
+ while (this.peek() && this.peek().type !== "RowEnd") {
219
+ cells.push(parseCell());
220
+ }
221
+ this.next(); // skip RowEnd token
222
+ return {
223
+ isHeader: rowStartToken.isHeader,
224
+ cells: cells
225
+ };
226
+ };
227
+ const parseCell = () => {
228
+ const cellStartToken = this.peek();
229
+ if (cellStartToken?.type !== "CellStart")
230
+ return { align: "left", chlidren: [] };
231
+ this.next(); // skip CellStart token
232
+ const childrens = this.parseInlineUntil("CellEnd");
233
+ return {
234
+ align: cellStartToken.align,
235
+ chlidren: [{ type: "Paragraph", children: childrens }]
236
+ };
237
+ };
238
+ const rows = [];
239
+ while (this.peek()?.type !== "TableEnd") {
240
+ rows.push(parseRow());
241
+ if (this.isEnd())
242
+ break;
243
+ }
244
+ this.next();
245
+ return {
246
+ type: "Table",
247
+ rows: rows
248
+ };
249
+ }
250
+ parseHorizontalLine() {
251
+ const tok = this.peek();
252
+ this.next(); // skip marker
253
+ return { type: "HorizontalLine" };
254
+ }
130
255
  parseInlineUntil(stopType) {
256
+ const stop = Array.isArray(stopType) ? stopType : [stopType];
131
257
  const listNode = [];
132
- while (!this.isEnd() && this.peek()?.type !== stopType) {
258
+ while (!this.isEnd()) {
133
259
  const currentNode = this.peek();
134
260
  if (!currentNode)
135
261
  break;
262
+ if (stop.includes(currentNode.type))
263
+ break;
136
264
  switch (currentNode.type) {
137
265
  case "Bold": {
138
266
  listNode.push(this.parseBold());
@@ -142,6 +270,10 @@ class Parser {
142
270
  listNode.push(this.parseItalic());
143
271
  break;
144
272
  }
273
+ case "Strikethrough": {
274
+ listNode.push(this.parseStrikethrough());
275
+ break;
276
+ }
145
277
  case "InlineCode": {
146
278
  listNode.push(this.parseInlineCode());
147
279
  break;
@@ -11,5 +11,6 @@ export default class Renderer {
11
11
  */
12
12
  render(node: Node): string;
13
13
  private handleRender;
14
+ private renderTable;
14
15
  private escapeHtml;
15
16
  }
package/dist/renderer.js CHANGED
@@ -19,20 +19,52 @@ 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
- Header: (node, children) => `<h${node.level}>${children.join("")}</h${node.level}>`,
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
+ //For list nodes
30
+ List: (node, children) => node.ordered ? `<ol>${children.join("")}</ol>` : `<ul>${children.join("")}</ul>`,
31
+ ListItem: (_node, children) => `<li>${children.join("")}</li>`,
32
+ TaskItem: (node, children) => `<li><input type="checkbox" disabled ${node.checked ? "checked" : ""}>${children.join("")}</li>`,
33
+ //Styling nodes
27
34
  Bold: (_node, children) => `<strong>${children.join("")}</strong>`,
28
35
  Italic: (_node, children) => `<em>${children.join("")}</em>`,
29
- Quote: (_node, children) => `<blockquote>${children.join("")}</blockquote>`,
36
+ Strikethrough: (_node, children) => `<s>${children.join("")}</s>`,
37
+ InlineCode: (node) => `<code>${this.escapeHtml(node.content)}</code>`,
38
+ //Media nodes
30
39
  Link: (node) => `<a href="${node.href}">${node.text}</a>`,
31
40
  Image: (node) => `<img src="${node.src}" alt="${node.alt}"/>`,
41
+ //Leaf nodes
42
+ HorizontalLine: (_node) => `<hr>`,
32
43
  Text: (node) => node.value,
44
+ //For table nodes
45
+ Table: (node, children) => this.renderTable(node, children),
33
46
  };
34
47
  return this.option.elements?.[type] ?? defaultRender[type];
35
48
  }
49
+ renderTable(node, children) {
50
+ if (node.type === "Table") {
51
+ const header = node.rows.filter(row => row.isHeader);
52
+ const body = node.rows.filter(row => !row.isHeader);
53
+ const renderRows = (row) => {
54
+ const tag = row.isHeader ? "th" : "td";
55
+ const cells = row.cells.map(cell => {
56
+ const align = `style="text-align:${cell.align}"`;
57
+ return `<${tag} ${align}>${cell.chlidren.map(c => this.render(c)).join("")}</${tag}>`;
58
+ }).join("");
59
+ return `<tr>${cells}</tr>`;
60
+ };
61
+ const tHead = header.length ? `<thead>${header.map(renderRows).join("")}</thead>` : "";
62
+ const tBody = body.length ? `<tbody>${body.map(renderRows).join("")}</tbody>` : "";
63
+ return `<table>${tHead}${tBody}</table>`;
64
+ }
65
+ else
66
+ return `<p>${children.join("\n")}</p>`;
67
+ }
36
68
  escapeHtml(str) {
37
69
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
38
70
  }
@@ -10,11 +10,16 @@
10
10
  * - Header: A header with given `level` (1-6)
11
11
  * - Bold: Bold text
12
12
  * - Italic: Italic text
13
+ * - Strikethrough: Strilethrough text
13
14
  * - InlineCode: Inline code snippet, with it's `content`
14
15
  * - Quote: A quote block
15
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
16
20
  * - Link: A link, with it's `text` and `href`
17
21
  * - Image: An image, with it's `src` and `alt`
22
+ * - HorizontalLine: A horizontal line
18
23
  * - Text: Raw text content.
19
24
  */
20
25
  export type Node = {
@@ -33,6 +38,9 @@ export type Node = {
33
38
  } | {
34
39
  type: "Italic";
35
40
  children: Node[];
41
+ } | {
42
+ type: "Strikethrough";
43
+ children: Node[];
36
44
  } | {
37
45
  type: "InlineCode";
38
46
  content: string;
@@ -43,6 +51,18 @@ export type Node = {
43
51
  } | {
44
52
  type: "Quote";
45
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[];
46
66
  } | {
47
67
  type: "Link";
48
68
  href: string;
@@ -51,7 +71,20 @@ export type Node = {
51
71
  type: "Image";
52
72
  src: string;
53
73
  alt: string;
74
+ } | {
75
+ type: "HorizontalLine";
54
76
  } | {
55
77
  type: "Text";
56
78
  value: string;
79
+ } | {
80
+ type: "Table";
81
+ rows: TableRow[];
82
+ };
83
+ export type TableRow = {
84
+ isHeader: boolean;
85
+ cells: TableCell[];
86
+ };
87
+ export type TableCell = {
88
+ align: "left" | "center" | "right";
89
+ chlidren: Node[];
57
90
  };
@@ -8,13 +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
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
16
20
  * - Link: A link (`[text](url)`)
17
21
  * - Image: An image (`![alt](url)`)
22
+ * - HorizontalLine: A horizontal line (`---` or `___` or `***`)
23
+ * - Text: Plain text content.
18
24
  * - EOF: A special token, this is the end of input.
19
25
  */
20
26
  export type Token = {
@@ -26,18 +32,28 @@ export type Token = {
26
32
  content: string;
27
33
  } | {
28
34
  type: "NewLine";
29
- } | {
30
- type: "Text";
31
- value: string;
32
35
  } | {
33
36
  type: "Bold";
34
37
  } | {
35
38
  type: "Italic";
39
+ } | {
40
+ type: "Strikethrough";
36
41
  } | {
37
42
  type: "InlineCode";
38
43
  content: string;
39
44
  } | {
40
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";
41
57
  } | {
42
58
  type: "Link";
43
59
  text: string;
@@ -46,6 +62,25 @@ export type Token = {
46
62
  type: "Image";
47
63
  src: string;
48
64
  alt: string;
65
+ } | {
66
+ type: "HorizontalLine";
67
+ } | {
68
+ type: "Text";
69
+ value: string;
49
70
  } | {
50
71
  type: "EOF";
72
+ } | {
73
+ type: "TableStart";
74
+ } | {
75
+ type: "TableEnd";
76
+ } | {
77
+ type: "RowStart";
78
+ isHeader: boolean;
79
+ } | {
80
+ type: "RowEnd";
81
+ } | {
82
+ type: "CellStart";
83
+ align: "left" | "center" | "right";
84
+ } | {
85
+ type: "CellEnd";
51
86
  };
package/package.json CHANGED
@@ -1,41 +1,41 @@
1
- {
2
- "name": "simple-customize-markdown-converter",
3
- "version": "1.0.2",
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.4",
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
  }