@rokelamen/md2html 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -3
- package/bin/cli.cjs +87 -18
- package/dist/index.js +83 -15
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,18 +1,50 @@
|
|
|
1
1
|
# md2html
|
|
2
2
|
|
|
3
|
-
A simple markdown-html
|
|
3
|
+
A simple markdown-html converter written in Typescript.
|
|
4
|
+
|
|
5
|
+
The style relies on browser pure style, inspired by [`txti`](https://txti.es/).
|
|
4
6
|
|
|
5
7
|
## Goal
|
|
6
8
|
|
|
7
9
|
> I create this project for learning TS and node dev, not for the purpose of building another better markdown parse engine.
|
|
8
10
|
|
|
9
|
-
Markdown syntax was first promoted with the release of `markdown.pl` by John Gruber. This leads to Markdown has no explicit definition, which means how markdown is parsed to HTML highly depends on the implementation of the tool. *And I
|
|
11
|
+
Markdown syntax was first promoted with the release of `markdown.pl` by John Gruber. This leads to Markdown has no explicit definition, which means how markdown is parsed to HTML highly depends on the implementation of the tool. *And I choose a simplest way(line-by-line parsing)*
|
|
10
12
|
|
|
11
13
|
To stay as close as possible to the 'Standard Markdown', [CommonMark](https://commonmark.org/) is a great reference.
|
|
12
14
|
|
|
15
|
+
## Requirement
|
|
16
|
+
|
|
17
|
+
[Node](https://nodejs.org/) environment(>= v12.0.0) is necessary.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
Using npm:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm i -g @rokelamen/md2html
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
md2html [options] [input]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
1. Parse from/to stdio
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
md2html "# Markdown content" > index.html
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
2. Parse from/to file
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
md2html -f input.md -o index.html
|
|
43
|
+
```
|
|
44
|
+
|
|
13
45
|
## Development Log
|
|
14
46
|
|
|
15
47
|
Why I choose to use [`rollup`](https://rollupjs.org/)?
|
|
16
48
|
|
|
17
|
-
A: Every time I `import` a module, I have to add extension to the module file and it is probably a `.js`
|
|
49
|
+
A: Every time I `import` a module, I have to add extension to the module file and it is probably a `.js` rather than `.ts`. It's wired that I must `import` a future JS file. So I decided to use a build tool(`rollup` of course) to pack all files together, which eliminates all `import` statements.
|
|
18
50
|
|
package/bin/cli.cjs
CHANGED
|
@@ -4338,6 +4338,7 @@ const {
|
|
|
4338
4338
|
|
|
4339
4339
|
/* For markdown line pattern pair */
|
|
4340
4340
|
const headingReg = /^\s*(#{1,6})(?:\s+|$)(.*)$/;
|
|
4341
|
+
const delimiterReg = /^\s*((?:\*+\s*){3,}|(?:-+\s*){3,}|(?:_+\s*){3,})$/;
|
|
4341
4342
|
const quoteReg = /^>\s*(.*)$/;
|
|
4342
4343
|
const ulistReg = /^\s*([-+*])(?:\s+|$)(.*)$/;
|
|
4343
4344
|
const olistReg = /^\s*(\d+)(.|\))(?:\s+|$)(.*)$/;
|
|
@@ -4359,7 +4360,65 @@ const italicReg = /([*_])([^*_]+)\1/g;
|
|
|
4359
4360
|
* This function is intended to remove all such representations.
|
|
4360
4361
|
*/
|
|
4361
4362
|
function escapeHtml(content) {
|
|
4362
|
-
return content
|
|
4363
|
+
return content
|
|
4364
|
+
.replace(/&/g, '&')
|
|
4365
|
+
.replace(/</g, '<')
|
|
4366
|
+
.replace(/>/g, '>')
|
|
4367
|
+
.replace(/"/g, '"')
|
|
4368
|
+
.replace(/'/g, ''');
|
|
4369
|
+
}
|
|
4370
|
+
const DEFAULT_STYLE = `
|
|
4371
|
+
body {
|
|
4372
|
+
margin: 0 auto;
|
|
4373
|
+
max-width: 650px;
|
|
4374
|
+
line-height: 1.6;
|
|
4375
|
+
font-size: 18px;
|
|
4376
|
+
color: #444;
|
|
4377
|
+
padding: 50px;
|
|
4378
|
+
}
|
|
4379
|
+
h1, h2, h3 {
|
|
4380
|
+
line-height: 1.2;
|
|
4381
|
+
}
|
|
4382
|
+
pre {
|
|
4383
|
+
background-color: #f8f8f8;
|
|
4384
|
+
padding: 18px;
|
|
4385
|
+
border-radius: 5px;
|
|
4386
|
+
}
|
|
4387
|
+
code {
|
|
4388
|
+
padding: 3px 5px;
|
|
4389
|
+
border-radius: 5px;
|
|
4390
|
+
background-color: #f4f4f4;
|
|
4391
|
+
font-size: 85%;
|
|
4392
|
+
}
|
|
4393
|
+
pre code {
|
|
4394
|
+
background-color: transparent;
|
|
4395
|
+
}
|
|
4396
|
+
blockquote {
|
|
4397
|
+
margin: 0;
|
|
4398
|
+
border-left: 5px solid #dfe2e5;
|
|
4399
|
+
padding: 0 18px;
|
|
4400
|
+
}
|
|
4401
|
+
blockquote > * {
|
|
4402
|
+
margin: 0;
|
|
4403
|
+
padding: 0;
|
|
4404
|
+
color: #888;
|
|
4405
|
+
}
|
|
4406
|
+
`;
|
|
4407
|
+
function wrapHtmlTemplate(content) {
|
|
4408
|
+
return `<!DOCTYPE html>
|
|
4409
|
+
<html>
|
|
4410
|
+
<head>
|
|
4411
|
+
<meta charset="UTF-8">
|
|
4412
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
4413
|
+
<title>Markdown preview</title>
|
|
4414
|
+
<style>
|
|
4415
|
+
${DEFAULT_STYLE.trim()}
|
|
4416
|
+
</style>
|
|
4417
|
+
</head>
|
|
4418
|
+
<body>
|
|
4419
|
+
${content.trim()}
|
|
4420
|
+
</body>
|
|
4421
|
+
</html>`;
|
|
4363
4422
|
}
|
|
4364
4423
|
|
|
4365
4424
|
/* traverse markdown content elements and wrap text with tags at proper positions. */
|
|
@@ -4368,35 +4427,38 @@ function renderToHtml(mdElements) {
|
|
|
4368
4427
|
for (const element of mdElements) {
|
|
4369
4428
|
const type = element.type;
|
|
4370
4429
|
switch (type) {
|
|
4371
|
-
case
|
|
4430
|
+
case 'text':
|
|
4372
4431
|
result += `<p>${inlineParse(element.content)}</p>\n`;
|
|
4373
4432
|
break;
|
|
4374
|
-
case
|
|
4433
|
+
case 'heading':
|
|
4375
4434
|
result += `<h${element.level}>${inlineParse(element.content)}</h${element.level}>\n`;
|
|
4376
4435
|
break;
|
|
4377
|
-
case
|
|
4378
|
-
result += `<
|
|
4436
|
+
case 'delimiter':
|
|
4437
|
+
result += `<hr>\n`;
|
|
4438
|
+
break;
|
|
4439
|
+
case 'quote':
|
|
4440
|
+
result += `<blockquote><p>${inlineParse(element.content)}</p></blockquote>\n`;
|
|
4379
4441
|
break;
|
|
4380
4442
|
case 'ulist':
|
|
4381
4443
|
result += '<ul>\n' +
|
|
4382
4444
|
element.items
|
|
4383
|
-
.map(item =>
|
|
4445
|
+
.map(item => `<li>${inlineParse(item)}</li>`)
|
|
4384
4446
|
.join('\n') +
|
|
4385
4447
|
'\n</ul>\n';
|
|
4386
4448
|
break;
|
|
4387
4449
|
case 'olist':
|
|
4388
4450
|
result += `<ol start="${element.start}">\n` +
|
|
4389
4451
|
element.items
|
|
4390
|
-
.map(item =>
|
|
4452
|
+
.map(item => `<li>${inlineParse(item)}</li>`)
|
|
4391
4453
|
.join('\n') +
|
|
4392
4454
|
'\n</ol>\n';
|
|
4393
4455
|
break;
|
|
4394
|
-
case
|
|
4395
|
-
result += '<
|
|
4456
|
+
case 'code':
|
|
4457
|
+
result += '<pre>\n' +
|
|
4396
4458
|
element.items
|
|
4397
|
-
.map(item =>
|
|
4459
|
+
.map(item => `<code>${escapeHtml(item)}</code>`)
|
|
4398
4460
|
.join('\n') +
|
|
4399
|
-
'\n</
|
|
4461
|
+
'\n</pre>\n';
|
|
4400
4462
|
break;
|
|
4401
4463
|
}
|
|
4402
4464
|
}
|
|
@@ -4414,7 +4476,7 @@ function inlineParse(content) {
|
|
|
4414
4476
|
};
|
|
4415
4477
|
// 1. code
|
|
4416
4478
|
content = content
|
|
4417
|
-
.replace(inlineCodeReg, (_, __, code) => stash(
|
|
4479
|
+
.replace(inlineCodeReg, (_, __, code) => stash(code));
|
|
4418
4480
|
// 2. link and emphasis
|
|
4419
4481
|
content = content
|
|
4420
4482
|
.replace(imgReg, '<img src="$2" alt="$1">')
|
|
@@ -4423,7 +4485,7 @@ function inlineParse(content) {
|
|
|
4423
4485
|
.replace(boldReg, '<strong>$2</strong>')
|
|
4424
4486
|
.replace(italicReg, '<em>$2</em>');
|
|
4425
4487
|
// 3. restore codes
|
|
4426
|
-
content = content.replace(/\u0000(\d+)\u0000/g, (_, i) => escapeHtml(placeholders[i]));
|
|
4488
|
+
content = content.replace(/\u0000(\d+)\u0000/g, (_, i) => `<code>${escapeHtml(placeholders[i])}</code>`);
|
|
4427
4489
|
return content;
|
|
4428
4490
|
}
|
|
4429
4491
|
|
|
@@ -4452,7 +4514,7 @@ function inlineParse(content) {
|
|
|
4452
4514
|
* is one paragraph as well.
|
|
4453
4515
|
*/
|
|
4454
4516
|
/* The main parse logic */
|
|
4455
|
-
function parse(markdown) {
|
|
4517
|
+
function parse(markdown, hasStyle) {
|
|
4456
4518
|
/* Split markdown content to many lines */
|
|
4457
4519
|
const crlfReg = /\r?\n/;
|
|
4458
4520
|
const lines = markdown.split(crlfReg);
|
|
@@ -4460,7 +4522,7 @@ function parse(markdown) {
|
|
|
4460
4522
|
const mdElements = parseToElements(lines);
|
|
4461
4523
|
// console.log(mdElements);
|
|
4462
4524
|
const html = renderToHtml(mdElements);
|
|
4463
|
-
return html;
|
|
4525
|
+
return hasStyle ? wrapHtmlTemplate(html) : html;
|
|
4464
4526
|
}
|
|
4465
4527
|
/**
|
|
4466
4528
|
* Traverse lines to turn to markdown elements with different well-designed structures
|
|
@@ -4491,6 +4553,12 @@ function parseToElements(lines) {
|
|
|
4491
4553
|
flush();
|
|
4492
4554
|
continue;
|
|
4493
4555
|
}
|
|
4556
|
+
// Delimiter
|
|
4557
|
+
if (delimiterReg.test(line)) {
|
|
4558
|
+
flush();
|
|
4559
|
+
mdElements.push({ type: 'delimiter' });
|
|
4560
|
+
continue;
|
|
4561
|
+
}
|
|
4494
4562
|
// Headings
|
|
4495
4563
|
const headingM = line.match(headingReg);
|
|
4496
4564
|
if (headingM) {
|
|
@@ -4586,8 +4654,8 @@ function parseToElements(lines) {
|
|
|
4586
4654
|
}
|
|
4587
4655
|
|
|
4588
4656
|
var name = "@rokelamen/md2html";
|
|
4589
|
-
var version = "0.
|
|
4590
|
-
var description = "A simple tool to convert
|
|
4657
|
+
var version = "0.2.0";
|
|
4658
|
+
var description = "A simple tool to convert Markdown content to HTML";
|
|
4591
4659
|
|
|
4592
4660
|
/* Command-line tool logic */
|
|
4593
4661
|
function command() {
|
|
@@ -4600,6 +4668,7 @@ function command() {
|
|
|
4600
4668
|
program
|
|
4601
4669
|
.option('-f, --file <path>', 'source file path')
|
|
4602
4670
|
.option('-o, --output <path>', 'output file path')
|
|
4671
|
+
.option('-s, --style', 'output full html struct with style')
|
|
4603
4672
|
.argument('[input]', 'input content');
|
|
4604
4673
|
/* Parse the cli options */
|
|
4605
4674
|
program.parse(process.argv);
|
|
@@ -4625,7 +4694,7 @@ function command() {
|
|
|
4625
4694
|
}
|
|
4626
4695
|
})()
|
|
4627
4696
|
: input;
|
|
4628
|
-
const html = parse(content);
|
|
4697
|
+
const html = parse(content, options.style);
|
|
4629
4698
|
if (typeof options.output === 'string') {
|
|
4630
4699
|
fs__namespace.writeFileSync(options.output, html, 'utf-8');
|
|
4631
4700
|
return;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* For markdown line pattern pair */
|
|
2
2
|
const headingReg = /^\s*(#{1,6})(?:\s+|$)(.*)$/;
|
|
3
|
+
const delimiterReg = /^\s*((?:\*+\s*){3,}|(?:-+\s*){3,}|(?:_+\s*){3,})$/;
|
|
3
4
|
const quoteReg = /^>\s*(.*)$/;
|
|
4
5
|
const ulistReg = /^\s*([-+*])(?:\s+|$)(.*)$/;
|
|
5
6
|
const olistReg = /^\s*(\d+)(.|\))(?:\s+|$)(.*)$/;
|
|
@@ -21,7 +22,65 @@ const italicReg = /([*_])([^*_]+)\1/g;
|
|
|
21
22
|
* This function is intended to remove all such representations.
|
|
22
23
|
*/
|
|
23
24
|
function escapeHtml(content) {
|
|
24
|
-
return content
|
|
25
|
+
return content
|
|
26
|
+
.replace(/&/g, '&')
|
|
27
|
+
.replace(/</g, '<')
|
|
28
|
+
.replace(/>/g, '>')
|
|
29
|
+
.replace(/"/g, '"')
|
|
30
|
+
.replace(/'/g, ''');
|
|
31
|
+
}
|
|
32
|
+
const DEFAULT_STYLE = `
|
|
33
|
+
body {
|
|
34
|
+
margin: 0 auto;
|
|
35
|
+
max-width: 650px;
|
|
36
|
+
line-height: 1.6;
|
|
37
|
+
font-size: 18px;
|
|
38
|
+
color: #444;
|
|
39
|
+
padding: 50px;
|
|
40
|
+
}
|
|
41
|
+
h1, h2, h3 {
|
|
42
|
+
line-height: 1.2;
|
|
43
|
+
}
|
|
44
|
+
pre {
|
|
45
|
+
background-color: #f8f8f8;
|
|
46
|
+
padding: 18px;
|
|
47
|
+
border-radius: 5px;
|
|
48
|
+
}
|
|
49
|
+
code {
|
|
50
|
+
padding: 3px 5px;
|
|
51
|
+
border-radius: 5px;
|
|
52
|
+
background-color: #f4f4f4;
|
|
53
|
+
font-size: 85%;
|
|
54
|
+
}
|
|
55
|
+
pre code {
|
|
56
|
+
background-color: transparent;
|
|
57
|
+
}
|
|
58
|
+
blockquote {
|
|
59
|
+
margin: 0;
|
|
60
|
+
border-left: 5px solid #dfe2e5;
|
|
61
|
+
padding: 0 18px;
|
|
62
|
+
}
|
|
63
|
+
blockquote > * {
|
|
64
|
+
margin: 0;
|
|
65
|
+
padding: 0;
|
|
66
|
+
color: #888;
|
|
67
|
+
}
|
|
68
|
+
`;
|
|
69
|
+
function wrapHtmlTemplate(content) {
|
|
70
|
+
return `<!DOCTYPE html>
|
|
71
|
+
<html>
|
|
72
|
+
<head>
|
|
73
|
+
<meta charset="UTF-8">
|
|
74
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
75
|
+
<title>Markdown preview</title>
|
|
76
|
+
<style>
|
|
77
|
+
${DEFAULT_STYLE.trim()}
|
|
78
|
+
</style>
|
|
79
|
+
</head>
|
|
80
|
+
<body>
|
|
81
|
+
${content.trim()}
|
|
82
|
+
</body>
|
|
83
|
+
</html>`;
|
|
25
84
|
}
|
|
26
85
|
|
|
27
86
|
/* traverse markdown content elements and wrap text with tags at proper positions. */
|
|
@@ -30,35 +89,38 @@ function renderToHtml(mdElements) {
|
|
|
30
89
|
for (const element of mdElements) {
|
|
31
90
|
const type = element.type;
|
|
32
91
|
switch (type) {
|
|
33
|
-
case
|
|
92
|
+
case 'text':
|
|
34
93
|
result += `<p>${inlineParse(element.content)}</p>\n`;
|
|
35
94
|
break;
|
|
36
|
-
case
|
|
95
|
+
case 'heading':
|
|
37
96
|
result += `<h${element.level}>${inlineParse(element.content)}</h${element.level}>\n`;
|
|
38
97
|
break;
|
|
39
|
-
case
|
|
40
|
-
result += `<
|
|
98
|
+
case 'delimiter':
|
|
99
|
+
result += `<hr>\n`;
|
|
100
|
+
break;
|
|
101
|
+
case 'quote':
|
|
102
|
+
result += `<blockquote><p>${inlineParse(element.content)}</p></blockquote>\n`;
|
|
41
103
|
break;
|
|
42
104
|
case 'ulist':
|
|
43
105
|
result += '<ul>\n' +
|
|
44
106
|
element.items
|
|
45
|
-
.map(item =>
|
|
107
|
+
.map(item => `<li>${inlineParse(item)}</li>`)
|
|
46
108
|
.join('\n') +
|
|
47
109
|
'\n</ul>\n';
|
|
48
110
|
break;
|
|
49
111
|
case 'olist':
|
|
50
112
|
result += `<ol start="${element.start}">\n` +
|
|
51
113
|
element.items
|
|
52
|
-
.map(item =>
|
|
114
|
+
.map(item => `<li>${inlineParse(item)}</li>`)
|
|
53
115
|
.join('\n') +
|
|
54
116
|
'\n</ol>\n';
|
|
55
117
|
break;
|
|
56
|
-
case
|
|
57
|
-
result += '<
|
|
118
|
+
case 'code':
|
|
119
|
+
result += '<pre>\n' +
|
|
58
120
|
element.items
|
|
59
|
-
.map(item =>
|
|
121
|
+
.map(item => `<code>${escapeHtml(item)}</code>`)
|
|
60
122
|
.join('\n') +
|
|
61
|
-
'\n</
|
|
123
|
+
'\n</pre>\n';
|
|
62
124
|
break;
|
|
63
125
|
}
|
|
64
126
|
}
|
|
@@ -76,7 +138,7 @@ function inlineParse(content) {
|
|
|
76
138
|
};
|
|
77
139
|
// 1. code
|
|
78
140
|
content = content
|
|
79
|
-
.replace(inlineCodeReg, (_, __, code) => stash(
|
|
141
|
+
.replace(inlineCodeReg, (_, __, code) => stash(code));
|
|
80
142
|
// 2. link and emphasis
|
|
81
143
|
content = content
|
|
82
144
|
.replace(imgReg, '<img src="$2" alt="$1">')
|
|
@@ -85,7 +147,7 @@ function inlineParse(content) {
|
|
|
85
147
|
.replace(boldReg, '<strong>$2</strong>')
|
|
86
148
|
.replace(italicReg, '<em>$2</em>');
|
|
87
149
|
// 3. restore codes
|
|
88
|
-
content = content.replace(/\u0000(\d+)\u0000/g, (_, i) => escapeHtml(placeholders[i]));
|
|
150
|
+
content = content.replace(/\u0000(\d+)\u0000/g, (_, i) => `<code>${escapeHtml(placeholders[i])}</code>`);
|
|
89
151
|
return content;
|
|
90
152
|
}
|
|
91
153
|
|
|
@@ -114,7 +176,7 @@ function inlineParse(content) {
|
|
|
114
176
|
* is one paragraph as well.
|
|
115
177
|
*/
|
|
116
178
|
/* The main parse logic */
|
|
117
|
-
function parse(markdown) {
|
|
179
|
+
function parse(markdown, hasStyle) {
|
|
118
180
|
/* Split markdown content to many lines */
|
|
119
181
|
const crlfReg = /\r?\n/;
|
|
120
182
|
const lines = markdown.split(crlfReg);
|
|
@@ -122,7 +184,7 @@ function parse(markdown) {
|
|
|
122
184
|
const mdElements = parseToElements(lines);
|
|
123
185
|
// console.log(mdElements);
|
|
124
186
|
const html = renderToHtml(mdElements);
|
|
125
|
-
return html;
|
|
187
|
+
return hasStyle ? wrapHtmlTemplate(html) : html;
|
|
126
188
|
}
|
|
127
189
|
/**
|
|
128
190
|
* Traverse lines to turn to markdown elements with different well-designed structures
|
|
@@ -153,6 +215,12 @@ function parseToElements(lines) {
|
|
|
153
215
|
flush();
|
|
154
216
|
continue;
|
|
155
217
|
}
|
|
218
|
+
// Delimiter
|
|
219
|
+
if (delimiterReg.test(line)) {
|
|
220
|
+
flush();
|
|
221
|
+
mdElements.push({ type: 'delimiter' });
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
156
224
|
// Headings
|
|
157
225
|
const headingM = line.match(headingReg);
|
|
158
226
|
if (headingM) {
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rokelamen/md2html",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
5
|
-
"description": "A simple tool to convert
|
|
4
|
+
"version": "0.2.0",
|
|
5
|
+
"description": "A simple tool to convert Markdown content to HTML",
|
|
6
6
|
"author": "rokelamen <rogerskelamen@gmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"main": "./dist/index.js",
|