simple-customize-markdown-converter 1.0.7 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +74 -17
  2. package/dist/core/lexer/handler.d.ts +23 -0
  3. package/dist/core/lexer/handler.js +272 -0
  4. package/dist/core/lexer/index.d.ts +42 -0
  5. package/dist/core/lexer/index.js +177 -0
  6. package/dist/core/lexer.d.ts +46 -0
  7. package/dist/core/lexer.js +433 -0
  8. package/dist/core/parser/handler.d.ts +19 -0
  9. package/dist/core/parser/handler.js +254 -0
  10. package/dist/core/parser/index.d.ts +33 -0
  11. package/dist/core/parser/index.js +149 -0
  12. package/dist/core/parser.d.ts +37 -0
  13. package/dist/core/parser.js +346 -0
  14. package/dist/core/renderer.d.ts +3 -0
  15. package/dist/core/renderer.js +99 -0
  16. package/dist/core/resolver/footnote-resolver.d.ts +15 -0
  17. package/dist/core/resolver/footnote-resolver.js +36 -0
  18. package/dist/core/resolver.d.ts +15 -0
  19. package/dist/core/resolver.js +36 -0
  20. package/dist/index.d.ts +6 -3
  21. package/dist/index.js +10 -7
  22. package/dist/react.d.ts +36 -0
  23. package/dist/react.js +56 -0
  24. package/dist/renderers/default/handler.d.ts +21 -0
  25. package/dist/renderers/default/handler.js +114 -0
  26. package/dist/renderers/default/index.d.ts +14 -0
  27. package/dist/renderers/default/index.js +117 -0
  28. package/dist/renderers/default.d.ts +26 -0
  29. package/dist/renderers/default.js +105 -0
  30. package/dist/renderers/index.d.ts +10 -0
  31. package/dist/renderers/index.js +2 -0
  32. package/dist/renderers/react/handler.d.ts +22 -0
  33. package/dist/renderers/react/handler.js +123 -0
  34. package/dist/renderers/react/index.d.ts +15 -0
  35. package/dist/renderers/react/index.js +123 -0
  36. package/dist/renderers/react.d.ts +26 -0
  37. package/dist/renderers/react.js +123 -0
  38. package/dist/types/options/converterOptions.d.ts +11 -0
  39. package/dist/types/options/converterOptions.js +2 -0
  40. package/dist/types/options/index.d.ts +10 -0
  41. package/dist/types/options/index.js +2 -0
  42. package/dist/types/options/reactRenderOptions.d.ts +50 -0
  43. package/dist/types/options/reactRenderOptions.js +2 -0
  44. package/dist/types/options/renderOptions.d.ts +86 -0
  45. package/dist/types/options/renderOptions.js +2 -0
  46. package/dist/types/parser.d.ts +132 -0
  47. package/dist/types/parser.js +2 -0
  48. package/dist/types/renderer.d.ts +12 -0
  49. package/dist/types/renderer.js +2 -0
  50. package/dist/types/token.d.ts +94 -74
  51. package/dist/utilities/parser-utils.d.ts +5 -0
  52. package/dist/utilities/parser-utils.js +65 -0
  53. package/dist/utilities/tokenizer-utils.d.ts +11 -0
  54. package/dist/utilities/tokenizer-utils.js +159 -0
  55. package/package.json +24 -3
@@ -1,3 +1,4 @@
1
+ import { ILexer } from "../core/lexer";
1
2
  /**
2
3
  * Token produced by the Markdown lexer.
3
4
  *
@@ -33,77 +34,96 @@
33
34
  * - FootnodeRef: The reference of a footnote
34
35
  * - EOF: A special token, this is the end of input.
35
36
  */
36
- export type Token = {
37
- type: "Header";
38
- level: number;
39
- } | {
40
- type: "CodeBlock";
41
- lang: string;
42
- content: string;
43
- } | {
44
- type: "NewLine";
45
- } | {
46
- type: "Bold";
47
- } | {
48
- type: "Italic";
49
- } | {
50
- type: "Strikethrough";
51
- } | {
52
- type: "InlineCode";
53
- content: string;
54
- } | {
55
- type: "Quote";
56
- } | {
57
- type: "ListStart";
58
- ordered: boolean;
59
- level: number;
60
- } | {
61
- type: "ListItem";
62
- } | {
63
- type: "TaskItem";
64
- checked: boolean;
65
- } | {
66
- type: "ListEnd";
67
- } | {
68
- type: "Link";
69
- text: string;
70
- href: string;
71
- } | {
72
- type: "Image";
73
- src: string;
74
- alt: string;
75
- } | {
76
- type: "HorizontalLine";
77
- } | {
78
- type: "Text";
79
- value: string;
80
- } | {
81
- type: "TableStart";
82
- } | {
83
- type: "TableEnd";
84
- } | {
85
- type: "RowStart";
86
- isHeader: boolean;
87
- } | {
88
- type: "RowEnd";
89
- } | {
90
- type: "CellStart";
91
- align: "left" | "center" | "right";
92
- } | {
93
- type: "CellEnd";
94
- } | {
95
- type: "HTMLBlock";
96
- value: string;
97
- } | {
98
- type: "HTMLInline";
99
- value: string;
100
- } | {
101
- type: "FootnoteDef";
102
- id: string;
103
- content: string;
104
- } | {
105
- type: "FootnoteRef";
106
- id: string;
107
- } | {
108
- type: "EOF";
109
- };
37
+ export type DefaultTokenType = "Header" | "CodeBlock" | "NewLine" | "Bold" | "Italic" | "Strikethrough" | "InlineCode" | "Quote" | "ListStart" | "ListItem" | "TaskItem" | "ListEnd" | "Link" | "Image" | "HorizontalLine" | "Text" | "TableStart" | "TableEnd" | "RowStart" | "RowEnd" | "CellStart" | "CellEnd" | "HTMLBlock" | "HTMLInline" | "FootnoteDef" | "FootnoteRef" | "EOF";
38
+ export type TokenType = DefaultTokenType | (string & {});
39
+ /**
40
+ * Token produced by the Markdown lexer.
41
+ *
42
+ * Tokens are the intermediate representation between raw text and the AST.
43
+ */
44
+ export interface Token {
45
+ /**
46
+ * The category of the token.
47
+ * @see {@link DefaultTokenType} for the list of built-in types.
48
+ */
49
+ type: TokenType;
50
+ /**
51
+ * Raw text or value associated with the token.
52
+ * Commonly used in: `Text`, `HTMLBlock`, `HTMLInline`.
53
+ */
54
+ value?: string;
55
+ /**
56
+ * The nesting level or importance.
57
+ * Commonly used in: `Header` (level 1-6).
58
+ */
59
+ level?: number;
60
+ /**
61
+ * Language identifier for code fences.
62
+ * Commonly used in: `CodeBlock`.
63
+ */
64
+ lang?: string;
65
+ /**
66
+ * The main body of text within a token.
67
+ * Commonly used in: `CodeBlock`, `InlineCode`.
68
+ */
69
+ content?: string;
70
+ /**
71
+ * Destination URL for links or images.
72
+ * Commonly used in: `Link`, `Image`.
73
+ */
74
+ href?: string;
75
+ /**
76
+ * Display text for links.
77
+ * Commonly used in: `Link`.
78
+ */
79
+ text?: string;
80
+ /**
81
+ * Source path/URL for images.
82
+ * Commonly used in: `Image`.
83
+ */
84
+ src?: string;
85
+ /**
86
+ * Accessible alternative text.
87
+ * Commonly used in: `Image`.
88
+ */
89
+ alt?: string;
90
+ /**
91
+ * Alignment for table cells.
92
+ * Commonly used in: `CellStart` ("left", "center", "right").
93
+ */
94
+ align?: "left" | "center" | "right";
95
+ /**
96
+ * For task lists, indicates completion.
97
+ * Commonly used in: `TaskItem` (true if `[x]`).
98
+ */
99
+ checked?: boolean;
100
+ /**
101
+ * Unique identifier for footnotes.
102
+ * Commonly used in: `FootnoteDef`, `FootnoteRef`.
103
+ */
104
+ id?: string;
105
+ /**
106
+ * All other properties used by custom `Token`.
107
+ */
108
+ [key: string]: any;
109
+ }
110
+ /**
111
+ * A Strategy pattern for handle tokenizing input.
112
+ * @property type - Strategy's type.
113
+ * @property match - A function check current cursor position matched the syntax to be processed by `emit` function.
114
+ * @property emit - A function handle tokenizing input to `Token`.
115
+ */
116
+ export interface TokenizerStrategy {
117
+ name: string;
118
+ /**
119
+ * Checks if the current cursor position in the Lexer matches this syntax
120
+ * @param lex The current `ILexer` instance providing access to the input string and cursor.
121
+ * @returns True if this strategy should handle the current input.
122
+ */
123
+ match: (lex: ILexer) => boolean;
124
+ /**
125
+ * Consumes the input and produce Tokens added in ILexer implementation class.
126
+ * @param lex The `ILexer` instance to advance the cursor and store results.
127
+ */
128
+ emit: (lex: ILexer) => void;
129
+ }
@@ -0,0 +1,5 @@
1
+ import { IParser } from "../core/parser";
2
+ import { ASTNode } from "../types/parser";
3
+ declare function parseList(parser: IParser): ASTNode;
4
+ declare function parseListItem(parser: IParser): ASTNode;
5
+ export { parseList, parseListItem };
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseList = parseList;
4
+ exports.parseListItem = parseListItem;
5
+ function parseList(parser) {
6
+ const tok = parser.peek(0);
7
+ if (tok?.type === "ListStart") {
8
+ parser.next(1); //skip marker
9
+ const result = {
10
+ type: "List",
11
+ level: tok.level,
12
+ ordered: tok.ordered,
13
+ children: [],
14
+ };
15
+ let nextToken = parser.peek(0);
16
+ while (!parser.isEnd()) {
17
+ if (nextToken?.type === "ListItem" || nextToken?.type === "TaskItem") {
18
+ result.children?.push(parseListItem(parser));
19
+ nextToken = parser.peek(0);
20
+ }
21
+ else if (nextToken?.type === "ListEnd") {
22
+ parser.next(1);
23
+ break;
24
+ }
25
+ else
26
+ break;
27
+ }
28
+ return result;
29
+ }
30
+ //Temp return
31
+ return {
32
+ type: "Text",
33
+ value: ""
34
+ };
35
+ }
36
+ function parseListItem(parser) {
37
+ const currentToken = parser.peek(0);
38
+ parser.next(1); // skip marker
39
+ const children = [];
40
+ while (!parser.isEnd()) {
41
+ const tok = parser.peek(0);
42
+ if (!tok)
43
+ break;
44
+ if (tok.type === "NewLine") {
45
+ parser.next(1);
46
+ break;
47
+ }
48
+ if (tok.type === "ListStart") {
49
+ children.push(parseList(parser));
50
+ continue;
51
+ }
52
+ if (["ListItem", "TaskItem", "ListEnd"].includes(tok.type)) {
53
+ break;
54
+ }
55
+ children.push(...parser.parseInlineUntil("NewLine", true));
56
+ }
57
+ return currentToken?.type === "TaskItem" ? {
58
+ type: "TaskItem",
59
+ checked: currentToken.type === "TaskItem" ? currentToken.checked : false,
60
+ children: children
61
+ } : {
62
+ type: "ListItem",
63
+ children: children
64
+ };
65
+ }
@@ -0,0 +1,11 @@
1
+ import { ILexer } from "../core/lexer";
2
+ declare function handleTextBlock(lex: ILexer): void;
3
+ declare function handleHtmlBlock(lex: ILexer): void;
4
+ declare function handleHtmlInline(lex: ILexer): void;
5
+ declare function handleList(lex: ILexer, isOrdered: boolean, isTask: boolean): void;
6
+ declare function handleStartList(lex: ILexer, isOrder: boolean): void;
7
+ declare function handleListItem(lex: ILexer): void;
8
+ declare function handleTaskItem(lex: ILexer, isChecked: boolean): void;
9
+ declare function handleEndList(lex: ILexer): void;
10
+ declare function handleTable(lex: ILexer): void;
11
+ export { handleEndList, handleHtmlBlock, handleHtmlInline, handleList, handleListItem, handleStartList, handleTable, handleTaskItem, handleTextBlock, };
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.handleEndList = handleEndList;
7
+ exports.handleHtmlBlock = handleHtmlBlock;
8
+ exports.handleHtmlInline = handleHtmlInline;
9
+ exports.handleList = handleList;
10
+ exports.handleListItem = handleListItem;
11
+ exports.handleStartList = handleStartList;
12
+ exports.handleTable = handleTable;
13
+ exports.handleTaskItem = handleTaskItem;
14
+ exports.handleTextBlock = handleTextBlock;
15
+ const lexer_1 = __importDefault(require("../core/lexer"));
16
+ function handleTextBlock(lex) {
17
+ const currentChar = lex.peek();
18
+ if (currentChar === null)
19
+ return;
20
+ const lastToken = lex.getLastToken();
21
+ if (lastToken?.type === "Text")
22
+ lastToken.value += currentChar;
23
+ else
24
+ lex.listToken.push({ type: "Text", value: currentChar });
25
+ }
26
+ function handleHtmlBlock(lex) {
27
+ const openTag = lex.readUntil(">", true) + ">";
28
+ const matchTagName = /^<\s*([a-zA-Z0-9]+)/.exec(openTag);
29
+ const tagName = matchTagName ? matchTagName[1] : null;
30
+ //Tagname is not valid
31
+ if (!tagName) {
32
+ lex.listToken.push({ type: "Text", value: "<" });
33
+ return;
34
+ }
35
+ //If it's self-closing tag
36
+ if (openTag.endsWith("/>") || ["hr", "img", "br", "input", "meta", "link"].includes(tagName)) {
37
+ lex.listToken.push({ type: "HTMLBlock", value: openTag });
38
+ return;
39
+ }
40
+ let content = "";
41
+ while (!lex.isEndOfFile()) {
42
+ if (lex.peekUntilByOffset(`</${tagName}>`.length).toLowerCase() === `</${tagName}>`) {
43
+ break;
44
+ }
45
+ content += lex.peek();
46
+ lex.next();
47
+ }
48
+ const closeTag = `</${tagName}>`;
49
+ lex.next(closeTag.length - 1); //Skip closing tag
50
+ lex.listToken.push({ type: "HTMLBlock", value: openTag + content + closeTag });
51
+ }
52
+ function handleHtmlInline(lex) {
53
+ const openTag = lex.readUntil(">", true) + ">";
54
+ const matchTagName = /^<\s*([a-zA-Z0-9]+)/.exec(openTag);
55
+ const tagName = matchTagName ? matchTagName[1] : null;
56
+ if (!tagName) {
57
+ lex.listToken.push({ type: "Text", value: "<" });
58
+ return;
59
+ }
60
+ const content = lex.readUntilMatchString(`</${tagName}>`, false);
61
+ const closeTag = `</${tagName}>`;
62
+ lex.next(closeTag.length - 1); //Skip closing tag
63
+ lex.listToken.push({ type: "HTMLInline", value: openTag + content + closeTag });
64
+ }
65
+ //Task and List utilities
66
+ function handleList(lex, isOrdered, isTask) {
67
+ const line = lex.peekUntil("\n");
68
+ if (isTask) {
69
+ const m = line.match(/^(\s*)([-*+]) \[( |x|X)\] (.*)$/);
70
+ const indent = Math.floor(m[1].length / 2) + 1;
71
+ while (lex.listLevelFlag < indent)
72
+ handleStartList(lex, false);
73
+ while (lex.listLevelFlag > indent)
74
+ handleEndList(lex);
75
+ lex.next(m[1].length + 4);
76
+ handleTaskItem(lex, m[3].toLowerCase() === "x");
77
+ }
78
+ else {
79
+ //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
80
+ const m = isOrdered ? line.match(/^(\s*)(\d+)\. (.*)$/) : line.match(/^(\s*)([-*+]) (.*)$/);
81
+ const indent = Math.floor(m[1].length / 2) + 1; //m[1] to get the spaces in group 1
82
+ while (lex.listLevelFlag < indent)
83
+ handleStartList(lex, isOrdered);
84
+ while (lex.listLevelFlag > indent)
85
+ handleEndList(lex);
86
+ lex.next(m[1].length + (isOrdered ? 1 : 0)); //+1 due to marker have 2 characters (e.g: 1.) instead 1 like unordered list
87
+ handleListItem(lex);
88
+ }
89
+ }
90
+ function handleStartList(lex, isOrder) {
91
+ lex.listLevelFlag++;
92
+ lex.listToken.push({ type: "ListStart", level: lex.listLevelFlag, ordered: isOrder });
93
+ }
94
+ function handleListItem(lex) {
95
+ lex.next(); // Skip space between - and text
96
+ lex.listToken.push({ type: "ListItem" });
97
+ }
98
+ function handleTaskItem(lex, isChecked) {
99
+ lex.next(); // Skip space between last ] and text
100
+ lex.listToken.push({ type: "TaskItem", checked: isChecked });
101
+ }
102
+ function handleEndList(lex) {
103
+ lex.listLevelFlag === 0 ? 0 : lex.listLevelFlag--;
104
+ lex.listToken.push({ type: "ListEnd" });
105
+ }
106
+ //Table utilities
107
+ function handleTable(lex) {
108
+ const tokenizeResult = [];
109
+ const handler = new lexer_1.default("");
110
+ const header = lex.readUntil("\n", true);
111
+ const headerDetails = header.trim().replace(/^ *\|/, "").replace(/\| *$/, "").split("|");
112
+ const align = lex.readUntil("\n", true);
113
+ const alignDetails = align.trim().replace(/^ *\|/, "").replace(/\| *$/, "").split("|");
114
+ if (alignDetails.length !== headerDetails.length || !alignDetails.every(c => /^:?-{3,}:?$/.test(c))) {
115
+ lex.listToken.push({ type: "Text", value: `${header}\n${align}\n` });
116
+ return;
117
+ }
118
+ else {
119
+ //Handle alignment
120
+ const normalizeAlign = alignDetails.map(value => {
121
+ if (value.startsWith(":") && value.endsWith(":"))
122
+ return "center";
123
+ else if (value.endsWith(":"))
124
+ return "right";
125
+ else
126
+ return "left";
127
+ });
128
+ tokenizeResult.push({ type: "TableStart" });
129
+ //Handle header
130
+ tokenizeResult.push({ type: "RowStart", isHeader: true });
131
+ headerDetails.forEach((cell, index) => {
132
+ tokenizeResult.push({ type: "CellStart", align: normalizeAlign[index] ?? "left" });
133
+ handler.setInput(cell.trim());
134
+ tokenizeResult.push(...handler.tokenize(false));
135
+ tokenizeResult.push({ type: "CellEnd" });
136
+ });
137
+ tokenizeResult.push({ type: "RowEnd" });
138
+ //Handle body
139
+ while (!lex.isEndOfFile()) {
140
+ const body = lex.readUntil("\n", true);
141
+ if (!body)
142
+ break;
143
+ const line = body.trim();
144
+ if (!line.startsWith("|") || !line.endsWith("|"))
145
+ break; //End of table
146
+ const bodyDetail = body.trim().replace(/^ *\|/, "").replace(/\| *$/, "").split("|");
147
+ tokenizeResult.push({ type: "RowStart", isHeader: false });
148
+ bodyDetail.forEach((cell, index) => {
149
+ tokenizeResult.push({ type: "CellStart", align: normalizeAlign[index] ?? "left" });
150
+ handler.setInput(cell.trim());
151
+ tokenizeResult.push(...handler.tokenize(false));
152
+ tokenizeResult.push({ type: "CellEnd" });
153
+ });
154
+ tokenizeResult.push({ type: "RowEnd" });
155
+ }
156
+ tokenizeResult.push({ type: "TableEnd" });
157
+ lex.listToken.push(...tokenizeResult);
158
+ }
159
+ }
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "simple-customize-markdown-converter",
3
- "version": "1.0.7",
4
- "description": "Convert Markdown to your customize HTML",
3
+ "version": "1.2.0",
4
+ "description": "Transform Markdown into fully customizable HTML or React components.",
5
5
  "keywords": [
6
6
  "markdown",
7
7
  "html",
8
- "converter"
8
+ "converter",
9
+ "react-markdown",
10
+ "typescript"
9
11
  ],
10
12
  "author": "Regiko04",
11
13
  "license": "MIT",
@@ -17,15 +19,34 @@
17
19
  "LICENSE",
18
20
  "README.md"
19
21
  ],
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "default": "./dist/index.js"
26
+ },
27
+ "./react": {
28
+ "types": "./dist/react.d.ts",
29
+ "default": "./dist/react.js"
30
+ }
31
+ },
20
32
  "scripts": {
21
33
  "test": "jest",
22
34
  "build": "tsc",
23
35
  "start": "ts-node src/index.ts"
24
36
  },
37
+ "peerDependencies": {
38
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.2.0",
39
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.2.0"
40
+ },
25
41
  "devDependencies": {
26
42
  "@types/jest": "^30.0.0",
27
43
  "@types/node": "^24.3.3",
44
+ "@types/react": "^19.2.7",
45
+ "@types/react-dom": "^19.2.3",
28
46
  "jest": "^30.1.3",
47
+ "jest-environment-jsdom": "^30.2.0",
48
+ "react": "^19.2.3",
49
+ "react-dom": "^19.2.3",
29
50
  "ts-jest": "^29.4.1",
30
51
  "ts-node": "^10.9.2",
31
52
  "typescript": "^5.9.2"