@leadertechie/md2html 0.1.0-alpha.0 → 0.1.0-alpha.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 +28 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/lit-renderer.d.ts +10 -0
- package/dist/lit-renderer.d.ts.map +1 -0
- package/dist/lit-renderer.js +80 -0
- package/dist/parser.d.ts +16 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +117 -0
- package/dist/pipeline.d.ts +18 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +58 -0
- package/dist/renderer.d.ts +15 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +102 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +6 -1
- package/.github/workflows/publish.yml +0 -76
- package/GitVersion.yml +0 -7
- package/__tests__/index.test.d.ts +0 -2
- package/__tests__/index.test.d.ts.map +0 -1
- package/__tests__/index.test.js +0 -130
- package/__tests__/index.test.ts +0 -155
- package/src/index.ts +0 -7
- package/src/lit-renderer.ts +0 -91
- package/src/parser.ts +0 -142
- package/src/pipeline.ts +0 -66
- package/src/renderer.ts +0 -67
- package/src/types.ts +0 -40
- package/tsconfig.json +0 -18
package/README.md
CHANGED
|
@@ -55,10 +55,35 @@ const pipeline = new MarkdownPipeline({
|
|
|
55
55
|
gfm: true,
|
|
56
56
|
breaks: false,
|
|
57
57
|
pedantic: false
|
|
58
|
+
},
|
|
59
|
+
styleOptions: {
|
|
60
|
+
classPrefix: 'md-',
|
|
61
|
+
customCSS: 'body { font-family: system-ui; }',
|
|
62
|
+
addHeadingIds: true
|
|
58
63
|
}
|
|
59
64
|
});
|
|
60
65
|
```
|
|
61
66
|
|
|
67
|
+
### Style Configuration Options
|
|
68
|
+
|
|
69
|
+
| Option | Type | Default | Description |
|
|
70
|
+
|--------|------|---------|-------------|
|
|
71
|
+
| `classPrefix` | string | `''` | Prefix for CSS classes on elements (e.g., `'md-'` produces `md-heading`, `md-paragraph`) |
|
|
72
|
+
| `customCSS` | string | `''` | Custom CSS string to inject (use `pipeline.getCustomCSS()` to retrieve) |
|
|
73
|
+
| `addHeadingIds` | boolean | `false` | Add ID attributes to headings based on their content for anchor links |
|
|
74
|
+
|
|
75
|
+
When `classPrefix` or `addHeadingIds` is set, CSS classes will be added to elements:
|
|
76
|
+
- `heading`, `paragraph`, `list`, `list-item`, `image`, `code`, `container`, `blockquote`
|
|
77
|
+
|
|
78
|
+
Example output with `classPrefix: 'md-'` and `addHeadingIds: true`:
|
|
79
|
+
```html
|
|
80
|
+
<h1 id="hello-world" class="md-heading">Hello World</h1>
|
|
81
|
+
<p class="md-paragraph">This is a paragraph.</p>
|
|
82
|
+
<ul class="md-list">
|
|
83
|
+
<li class="md-list-item">Item 1</li>
|
|
84
|
+
</ul>
|
|
85
|
+
```
|
|
86
|
+
|
|
62
87
|
### API
|
|
63
88
|
|
|
64
89
|
| Method | Description |
|
|
@@ -66,7 +91,9 @@ const pipeline = new MarkdownPipeline({
|
|
|
66
91
|
| `parse(markdown)` | Parse markdown string to AST |
|
|
67
92
|
| `render(nodes)` | Render AST to HTML string |
|
|
68
93
|
| `renderMarkdown(markdown)` | Parse and render in one call |
|
|
69
|
-
| `renderPage(title, nodes)` | Render AST to full HTML page |
|
|
94
|
+
| `renderPage(title, nodes, options?)` | Render AST to full HTML page |
|
|
95
|
+
| `getCustomCSS()` | Get custom CSS string from style config |
|
|
96
|
+
| `getConfig()` | Get current pipeline configuration |
|
|
70
97
|
|
|
71
98
|
## License
|
|
72
99
|
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAE9B,OAAO,EAAE,WAAW,IAAI,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TemplateResult } from 'lit';
|
|
2
|
+
import { ContentNode } from './types';
|
|
3
|
+
export declare class LitRenderer {
|
|
4
|
+
private renderTextNode;
|
|
5
|
+
renderNode(node: ContentNode): TemplateResult;
|
|
6
|
+
renderNodes(nodes: ContentNode[]): TemplateResult;
|
|
7
|
+
renderToHTMLString(nodes: ContentNode[]): string;
|
|
8
|
+
private nodeToHTMLString;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=lit-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lit-renderer.d.ts","sourceRoot":"","sources":["../src/lit-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAQ,MAAM,KAAK,CAAC;AAE3C,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC,qBAAa,WAAW;IACtB,OAAO,CAAC,cAAc;IAOtB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,cAAc;IAyC7C,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,cAAc;IAOjD,kBAAkB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,MAAM;IAOhD,OAAO,CAAC,gBAAgB;CAuBzB"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
3
|
+
export class LitRenderer {
|
|
4
|
+
renderTextNode(node) {
|
|
5
|
+
if (node.type === 'image') {
|
|
6
|
+
return html `<img src="${node.src}" alt="${node.alt || ''}" class="inline-image" style="max-width:100%;height:auto;">`;
|
|
7
|
+
}
|
|
8
|
+
return html `${unsafeHTML(node.content || '')}`;
|
|
9
|
+
}
|
|
10
|
+
renderNode(node) {
|
|
11
|
+
switch (node.type) {
|
|
12
|
+
case 'heading': {
|
|
13
|
+
const level = node.attributes?.level || '2';
|
|
14
|
+
if (level === '1')
|
|
15
|
+
return html `<h1>${unsafeHTML(node.content)}</h1>`;
|
|
16
|
+
if (level === '2')
|
|
17
|
+
return html `<h2>${unsafeHTML(node.content)}</h2>`;
|
|
18
|
+
if (level === '3')
|
|
19
|
+
return html `<h3>${unsafeHTML(node.content)}</h3>`;
|
|
20
|
+
return html `<h2>${unsafeHTML(node.content)}</h2>`;
|
|
21
|
+
}
|
|
22
|
+
case 'paragraph':
|
|
23
|
+
if (node.children) {
|
|
24
|
+
return html `<p>${node.children.map(child => this.renderTextNode(child))}</p>`;
|
|
25
|
+
}
|
|
26
|
+
return html `<p>${unsafeHTML(node.content)}</p>`;
|
|
27
|
+
case 'list':
|
|
28
|
+
return html `<ul>${node.children?.map(child => this.renderNode(child))}</ul>`;
|
|
29
|
+
case 'list-item':
|
|
30
|
+
return html `<li>${unsafeHTML(node.content)}</li>`;
|
|
31
|
+
case 'image':
|
|
32
|
+
return html `<img src="${node.src || node.attributes?.src}" alt="${node.alt || node.attributes?.alt || ''}" class="${node.className || ''}" style="max-width:100%;height:auto;">`;
|
|
33
|
+
case 'container':
|
|
34
|
+
return html `<div class="${node.className || ''}" style="${node.attributes?.style || ''}">
|
|
35
|
+
${node.children?.map(child => this.renderNode(child))}
|
|
36
|
+
</div>`;
|
|
37
|
+
case 'code':
|
|
38
|
+
return html `<pre><code class="language-${node.attributes?.lang || ''}">${node.content || ''}</code></pre>`;
|
|
39
|
+
case 'text':
|
|
40
|
+
return html `${node.content}`;
|
|
41
|
+
default:
|
|
42
|
+
return html ``;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
renderNodes(nodes) {
|
|
46
|
+
if (!nodes || nodes.length === 0) {
|
|
47
|
+
return html ``;
|
|
48
|
+
}
|
|
49
|
+
return html `${nodes.map(node => this.renderNode(node))}`;
|
|
50
|
+
}
|
|
51
|
+
renderToHTMLString(nodes) {
|
|
52
|
+
if (!nodes || nodes.length === 0) {
|
|
53
|
+
return '';
|
|
54
|
+
}
|
|
55
|
+
return nodes.map(node => this.nodeToHTMLString(node)).join('\n');
|
|
56
|
+
}
|
|
57
|
+
nodeToHTMLString(node) {
|
|
58
|
+
switch (node.type) {
|
|
59
|
+
case 'heading':
|
|
60
|
+
const level = node.attributes?.level || '2';
|
|
61
|
+
return `<h${level}>${node.content}</h${level}>`;
|
|
62
|
+
case 'paragraph':
|
|
63
|
+
return `<p>${node.content}</p>`;
|
|
64
|
+
case 'list':
|
|
65
|
+
const items = node.children?.map(child => this.nodeToHTMLString(child)).join('') || '';
|
|
66
|
+
return `<ul>${items}</ul>`;
|
|
67
|
+
case 'list-item':
|
|
68
|
+
return `<li>${node.content}</li>`;
|
|
69
|
+
case 'image':
|
|
70
|
+
return `<img src="${node.attributes?.src}" alt="${node.attributes?.alt}" class="${node.className || ''}">`;
|
|
71
|
+
case 'container':
|
|
72
|
+
const childrenHTML = node.children?.map(child => this.nodeToHTMLString(child)).join('') || '';
|
|
73
|
+
return `<div class="${node.className || ''}">${childrenHTML}</div>`;
|
|
74
|
+
case 'text':
|
|
75
|
+
return node.content || '';
|
|
76
|
+
default:
|
|
77
|
+
return '';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
package/dist/parser.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ContentNode, MarkdownContent, ParseOptions } from './types';
|
|
2
|
+
export declare class MarkdownParser {
|
|
3
|
+
private imagePathPrefix;
|
|
4
|
+
private imageBaseUrl;
|
|
5
|
+
constructor(options?: {
|
|
6
|
+
imagePathPrefix?: string;
|
|
7
|
+
imageBaseUrl?: string;
|
|
8
|
+
});
|
|
9
|
+
private processImagePath;
|
|
10
|
+
private processInlineFormatting;
|
|
11
|
+
private parseTokens;
|
|
12
|
+
private parseToken;
|
|
13
|
+
parse(markdown: string, options?: ParseOptions): MarkdownContent;
|
|
14
|
+
parseToNodes(markdown: string, options?: ParseOptions): ContentNode[];
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAErE,qBAAa,cAAc;IACzB,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,YAAY,CAAS;gBAEjB,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE;IAKzE,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,uBAAuB;IAM/B,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,UAAU;IAgFlB,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,eAAe;IAgBhE,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,WAAW,EAAE;CAGtE"}
|
package/dist/parser.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { marked } from 'marked';
|
|
2
|
+
export class MarkdownParser {
|
|
3
|
+
constructor(options) {
|
|
4
|
+
this.imagePathPrefix = options?.imagePathPrefix || '';
|
|
5
|
+
this.imageBaseUrl = options?.imageBaseUrl || '';
|
|
6
|
+
}
|
|
7
|
+
processImagePath(src) {
|
|
8
|
+
if (src.startsWith('http') || src.startsWith('/')) {
|
|
9
|
+
return src;
|
|
10
|
+
}
|
|
11
|
+
let path = this.imagePathPrefix ? `${this.imagePathPrefix}${src}` : src;
|
|
12
|
+
if (this.imageBaseUrl && !path.startsWith('http')) {
|
|
13
|
+
path = `${this.imageBaseUrl}${path}`;
|
|
14
|
+
}
|
|
15
|
+
return path;
|
|
16
|
+
}
|
|
17
|
+
processInlineFormatting(text) {
|
|
18
|
+
return text
|
|
19
|
+
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
|
20
|
+
.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
|
21
|
+
}
|
|
22
|
+
parseTokens(tokens) {
|
|
23
|
+
const nodes = [];
|
|
24
|
+
for (const token of tokens) {
|
|
25
|
+
const node = this.parseToken(token);
|
|
26
|
+
if (node) {
|
|
27
|
+
nodes.push(node);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return nodes;
|
|
31
|
+
}
|
|
32
|
+
parseToken(token) {
|
|
33
|
+
switch (token.type) {
|
|
34
|
+
case 'heading':
|
|
35
|
+
return {
|
|
36
|
+
type: 'heading',
|
|
37
|
+
content: token.text,
|
|
38
|
+
attributes: { level: String(token.depth) }
|
|
39
|
+
};
|
|
40
|
+
case 'paragraph':
|
|
41
|
+
const tokens = token.tokens || [];
|
|
42
|
+
const hasInlineImage = tokens.some(t => t.type === 'image');
|
|
43
|
+
if (hasInlineImage) {
|
|
44
|
+
const children = tokens.map(t => {
|
|
45
|
+
if (t.type === 'image') {
|
|
46
|
+
return {
|
|
47
|
+
type: 'image',
|
|
48
|
+
src: this.processImagePath(t.href),
|
|
49
|
+
alt: t.text || ''
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
type: 'text',
|
|
54
|
+
content: this.processInlineFormatting(t.text || '')
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
type: 'paragraph',
|
|
59
|
+
children
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
type: 'paragraph',
|
|
64
|
+
content: this.processInlineFormatting(token.text)
|
|
65
|
+
};
|
|
66
|
+
case 'list':
|
|
67
|
+
return {
|
|
68
|
+
type: 'list',
|
|
69
|
+
ordered: token.ordered,
|
|
70
|
+
children: token.items.map((item) => ({
|
|
71
|
+
type: 'list-item',
|
|
72
|
+
content: this.processInlineFormatting(item.text)
|
|
73
|
+
}))
|
|
74
|
+
};
|
|
75
|
+
case 'image':
|
|
76
|
+
return {
|
|
77
|
+
type: 'image',
|
|
78
|
+
src: this.processImagePath(token.href),
|
|
79
|
+
alt: token.title || ''
|
|
80
|
+
};
|
|
81
|
+
case 'code':
|
|
82
|
+
return {
|
|
83
|
+
type: 'code',
|
|
84
|
+
content: token.text,
|
|
85
|
+
attributes: { lang: token.lang || '' }
|
|
86
|
+
};
|
|
87
|
+
case 'hr':
|
|
88
|
+
return { type: 'container', attributes: { tag: 'hr' } };
|
|
89
|
+
case 'blockquote':
|
|
90
|
+
return {
|
|
91
|
+
type: 'container',
|
|
92
|
+
attributes: { tag: 'blockquote' },
|
|
93
|
+
children: this.parseTokens(token.tokens || [])
|
|
94
|
+
};
|
|
95
|
+
case 'html':
|
|
96
|
+
return { type: 'container', content: token.raw };
|
|
97
|
+
default:
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
parse(markdown, options) {
|
|
102
|
+
const parseOptions = {
|
|
103
|
+
gfm: options?.gfm ?? true,
|
|
104
|
+
breaks: options?.breaks ?? false,
|
|
105
|
+
pedantic: options?.pedantic ?? false
|
|
106
|
+
};
|
|
107
|
+
const tokens = marked.lexer(markdown, parseOptions);
|
|
108
|
+
const content = this.parseTokens(tokens);
|
|
109
|
+
return {
|
|
110
|
+
title: '',
|
|
111
|
+
content
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
parseToNodes(markdown, options) {
|
|
115
|
+
return this.parse(markdown, options).content;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ContentNode, MarkdownContent, PipelineConfig } from './types.js';
|
|
2
|
+
export declare class MarkdownPipeline {
|
|
3
|
+
private parser;
|
|
4
|
+
private renderer;
|
|
5
|
+
private config;
|
|
6
|
+
constructor(config?: PipelineConfig);
|
|
7
|
+
parse(markdown: string): ContentNode[];
|
|
8
|
+
parseWithMetadata(markdown: string): MarkdownContent;
|
|
9
|
+
render(nodes: ContentNode[]): string;
|
|
10
|
+
renderMarkdown(markdown: string): string;
|
|
11
|
+
renderPage(title: string, nodes: ContentNode[], options?: {
|
|
12
|
+
lang?: string;
|
|
13
|
+
charset?: string;
|
|
14
|
+
}): string;
|
|
15
|
+
getConfig(): Readonly<Required<PipelineConfig>>;
|
|
16
|
+
getCustomCSS(): string;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,cAAc,EAAe,MAAM,YAAY,CAAC;AAEvF,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,MAAM,CAA2B;gBAE7B,MAAM,GAAE,cAAmB;IAuBvC,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,EAAE;IAItC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe;IAIpD,MAAM,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,MAAM;IAIpC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAKxC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,OAAO,CAAC,EAAE;QACxD,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,MAAM;IAeV,SAAS,IAAI,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;IAI/C,YAAY,IAAI,MAAM;CAGvB"}
|
package/dist/pipeline.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { MarkdownParser } from './parser.js';
|
|
2
|
+
import { HTMLRenderer } from './renderer.js';
|
|
3
|
+
export class MarkdownPipeline {
|
|
4
|
+
constructor(config = {}) {
|
|
5
|
+
this.config = {
|
|
6
|
+
imagePathPrefix: config.imagePathPrefix || '',
|
|
7
|
+
imageBaseUrl: config.imageBaseUrl || '',
|
|
8
|
+
parseOptions: {
|
|
9
|
+
gfm: config.parseOptions?.gfm ?? true,
|
|
10
|
+
breaks: config.parseOptions?.breaks ?? false,
|
|
11
|
+
pedantic: config.parseOptions?.pedantic ?? false
|
|
12
|
+
},
|
|
13
|
+
styleOptions: {
|
|
14
|
+
classPrefix: config.styleOptions?.classPrefix || '',
|
|
15
|
+
customCSS: config.styleOptions?.customCSS || '',
|
|
16
|
+
addHeadingIds: config.styleOptions?.addHeadingIds ?? false
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
this.parser = new MarkdownParser({
|
|
20
|
+
imagePathPrefix: this.config.imagePathPrefix,
|
|
21
|
+
imageBaseUrl: this.config.imageBaseUrl
|
|
22
|
+
});
|
|
23
|
+
this.renderer = new HTMLRenderer(this.config.styleOptions);
|
|
24
|
+
}
|
|
25
|
+
parse(markdown) {
|
|
26
|
+
return this.parser.parseToNodes(markdown, this.config.parseOptions);
|
|
27
|
+
}
|
|
28
|
+
parseWithMetadata(markdown) {
|
|
29
|
+
return this.parser.parse(markdown, this.config.parseOptions);
|
|
30
|
+
}
|
|
31
|
+
render(nodes) {
|
|
32
|
+
return this.renderer.renderNodes(nodes);
|
|
33
|
+
}
|
|
34
|
+
renderMarkdown(markdown) {
|
|
35
|
+
const nodes = this.parse(markdown);
|
|
36
|
+
return this.render(nodes);
|
|
37
|
+
}
|
|
38
|
+
renderPage(title, nodes, options) {
|
|
39
|
+
const html = this.render(nodes);
|
|
40
|
+
return `<!DOCTYPE html>
|
|
41
|
+
<html lang="${options?.lang || 'en'}">
|
|
42
|
+
<head>
|
|
43
|
+
<meta charset="${options?.charset || 'UTF-8'}">
|
|
44
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
45
|
+
<title>${title}</title>
|
|
46
|
+
</head>
|
|
47
|
+
<body>
|
|
48
|
+
${html}
|
|
49
|
+
</body>
|
|
50
|
+
</html>`;
|
|
51
|
+
}
|
|
52
|
+
getConfig() {
|
|
53
|
+
return { ...this.config };
|
|
54
|
+
}
|
|
55
|
+
getCustomCSS() {
|
|
56
|
+
return this.renderer.getCustomCSS();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ContentNode, StyleConfig } from './types.js';
|
|
2
|
+
export declare class HTMLRenderer {
|
|
3
|
+
private config;
|
|
4
|
+
constructor(config?: StyleConfig);
|
|
5
|
+
private hasClassConfig;
|
|
6
|
+
private getClass;
|
|
7
|
+
private generateHeadingId;
|
|
8
|
+
private renderWithClass;
|
|
9
|
+
renderNode(node: ContentNode): string;
|
|
10
|
+
renderNodes(nodes: ContentNode[]): string;
|
|
11
|
+
renderToHTMLString(nodes: ContentNode[]): string;
|
|
12
|
+
render(markdown: string): string;
|
|
13
|
+
getCustomCSS(): string;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEtD,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAwB;gBAE1B,MAAM,GAAE,WAAgB;IAQpC,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,QAAQ;IAUhB,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,eAAe;IAOvB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM;IA4DrC,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,MAAM;IAOzC,kBAAkB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,MAAM;IAIhD,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAIhC,YAAY,IAAI,MAAM;CAGvB"}
|
package/dist/renderer.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export class HTMLRenderer {
|
|
2
|
+
constructor(config = {}) {
|
|
3
|
+
this.config = {
|
|
4
|
+
classPrefix: config.classPrefix || '',
|
|
5
|
+
customCSS: config.customCSS || '',
|
|
6
|
+
addHeadingIds: config.addHeadingIds ?? false
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
hasClassConfig() {
|
|
10
|
+
return this.config.classPrefix !== '' || this.config.addHeadingIds;
|
|
11
|
+
}
|
|
12
|
+
getClass(baseClass, nodeClass) {
|
|
13
|
+
if (!this.hasClassConfig()) {
|
|
14
|
+
return nodeClass || '';
|
|
15
|
+
}
|
|
16
|
+
const prefix = this.config.classPrefix;
|
|
17
|
+
const classes = [prefix ? `${prefix}${baseClass}` : baseClass];
|
|
18
|
+
if (nodeClass)
|
|
19
|
+
classes.push(nodeClass);
|
|
20
|
+
return classes.join(' ');
|
|
21
|
+
}
|
|
22
|
+
generateHeadingId(content) {
|
|
23
|
+
if (!content)
|
|
24
|
+
return '';
|
|
25
|
+
return content
|
|
26
|
+
.toLowerCase()
|
|
27
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
28
|
+
.replace(/(^-|-$)/g, '');
|
|
29
|
+
}
|
|
30
|
+
renderWithClass(tag, content, baseClass, nodeClass, extraAttrs) {
|
|
31
|
+
const classAttr = this.hasClassConfig() && baseClass
|
|
32
|
+
? ` class="${this.getClass(baseClass, nodeClass)}"`
|
|
33
|
+
: '';
|
|
34
|
+
return `<${tag}${classAttr}${extraAttrs || ''}>${content}</${tag}>`;
|
|
35
|
+
}
|
|
36
|
+
renderNode(node) {
|
|
37
|
+
switch (node.type) {
|
|
38
|
+
case 'heading':
|
|
39
|
+
const level = node.attributes?.level || '2';
|
|
40
|
+
const headingId = this.config.addHeadingIds
|
|
41
|
+
? ` id="${this.generateHeadingId(node.content)}"`
|
|
42
|
+
: '';
|
|
43
|
+
if (!this.hasClassConfig()) {
|
|
44
|
+
return `<h${level}${headingId}>${node.content || ''}</h${level}>`;
|
|
45
|
+
}
|
|
46
|
+
return `<h${level}${headingId} class="${this.getClass('heading')}">${node.content || ''}</h${level}>`;
|
|
47
|
+
case 'paragraph':
|
|
48
|
+
if (node.children) {
|
|
49
|
+
const childrenHtml = node.children.map(child => this.renderNode(child)).join('');
|
|
50
|
+
return this.renderWithClass('p', childrenHtml, 'paragraph');
|
|
51
|
+
}
|
|
52
|
+
return this.renderWithClass('p', node.content || '', 'paragraph');
|
|
53
|
+
case 'list':
|
|
54
|
+
const tag = node.ordered ? 'ol' : 'ul';
|
|
55
|
+
const items = node.children?.map(child => this.renderNode(child)).join('') || '';
|
|
56
|
+
return this.renderWithClass(tag, items, 'list');
|
|
57
|
+
case 'list-item':
|
|
58
|
+
return this.renderWithClass('li', node.content || '', 'list-item');
|
|
59
|
+
case 'image':
|
|
60
|
+
const src = node.src || node.attributes?.src || '';
|
|
61
|
+
const alt = node.alt || node.attributes?.alt || '';
|
|
62
|
+
const classStr = this.getClass('image', node.className || undefined);
|
|
63
|
+
return `<img src="${src}" alt="${alt}"${classStr ? ` class="${classStr}"` : ''}>`;
|
|
64
|
+
case 'code':
|
|
65
|
+
const codeClass = this.hasClassConfig()
|
|
66
|
+
? ` class="${this.getClass('code')} language-${node.attributes?.lang || ''}"`
|
|
67
|
+
: ` class="language-${node.attributes?.lang || ''}"`;
|
|
68
|
+
return `<pre><code${codeClass}>${node.content || ''}</code></pre>`;
|
|
69
|
+
case 'container':
|
|
70
|
+
if (node.attributes?.tag === 'hr')
|
|
71
|
+
return '<hr>';
|
|
72
|
+
if (node.attributes?.tag === 'blockquote') {
|
|
73
|
+
const children = node.children?.map(child => this.renderNode(child)).join('') || '';
|
|
74
|
+
return this.renderWithClass('blockquote', children, 'blockquote');
|
|
75
|
+
}
|
|
76
|
+
const containerChildren = node.children?.map(child => this.renderNode(child)).join('') || '';
|
|
77
|
+
return this.renderWithClass('div', containerChildren, 'container', node.className || undefined);
|
|
78
|
+
case 'strong':
|
|
79
|
+
return `<strong>${node.content || ''}</strong>`;
|
|
80
|
+
case 'emphasis':
|
|
81
|
+
return `<em>${node.content || ''}</em>`;
|
|
82
|
+
case 'text':
|
|
83
|
+
default:
|
|
84
|
+
return node.content || '';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
renderNodes(nodes) {
|
|
88
|
+
if (!nodes || nodes.length === 0) {
|
|
89
|
+
return '';
|
|
90
|
+
}
|
|
91
|
+
return nodes.map(node => this.renderNode(node)).join('\n');
|
|
92
|
+
}
|
|
93
|
+
renderToHTMLString(nodes) {
|
|
94
|
+
return this.renderNodes(nodes);
|
|
95
|
+
}
|
|
96
|
+
render(markdown) {
|
|
97
|
+
return markdown;
|
|
98
|
+
}
|
|
99
|
+
getCustomCSS() {
|
|
100
|
+
return this.config.customCSS;
|
|
101
|
+
}
|
|
102
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type ContentNodeType = 'text' | 'heading' | 'paragraph' | 'list' | 'list-item' | 'image' | 'code' | 'container' | 'strong' | 'emphasis';
|
|
2
|
+
export interface ContentNode {
|
|
3
|
+
type: ContentNodeType;
|
|
4
|
+
content?: string;
|
|
5
|
+
children?: ContentNode[];
|
|
6
|
+
attributes?: Record<string, unknown>;
|
|
7
|
+
className?: string;
|
|
8
|
+
src?: string;
|
|
9
|
+
alt?: string;
|
|
10
|
+
ordered?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface MarkdownContent {
|
|
13
|
+
title: string;
|
|
14
|
+
metadata?: Record<string, unknown>;
|
|
15
|
+
content: ContentNode[];
|
|
16
|
+
}
|
|
17
|
+
export interface ParseOptions {
|
|
18
|
+
gfm?: boolean;
|
|
19
|
+
breaks?: boolean;
|
|
20
|
+
pedantic?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface StyleConfig {
|
|
23
|
+
classPrefix?: string;
|
|
24
|
+
customCSS?: string;
|
|
25
|
+
addHeadingIds?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface PipelineConfig {
|
|
28
|
+
imagePathPrefix?: string;
|
|
29
|
+
imageBaseUrl?: string;
|
|
30
|
+
parseOptions?: ParseOptions;
|
|
31
|
+
styleOptions?: StyleConfig;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB,MAAM,GACN,SAAS,GACT,WAAW,GACX,MAAM,GACN,WAAW,GACX,OAAO,GACP,MAAM,GACN,WAAW,GACX,QAAQ,GACR,UAAU,CAAC;AAEf,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,YAAY,CAAC,EAAE,WAAW,CAAC;CAC5B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leadertechie/md2html",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.3",
|
|
4
4
|
"description": "Markdown to HTML pipeline - parse markdown to AST, render to HTML or Lit templates",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -36,6 +36,11 @@
|
|
|
36
36
|
"ssr"
|
|
37
37
|
],
|
|
38
38
|
"license": "MIT",
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"README.md",
|
|
42
|
+
"LICENSE"
|
|
43
|
+
],
|
|
39
44
|
"repository": {
|
|
40
45
|
"type": "git",
|
|
41
46
|
"url": "git+https://github.com/leadertechie/md2html.git"
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
name: CI / Publish
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- '**'
|
|
7
|
-
pull_request:
|
|
8
|
-
workflow_dispatch:
|
|
9
|
-
|
|
10
|
-
jobs:
|
|
11
|
-
build-and-test:
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
permissions:
|
|
14
|
-
contents: read
|
|
15
|
-
|
|
16
|
-
steps:
|
|
17
|
-
- uses: actions/checkout@v4
|
|
18
|
-
|
|
19
|
-
- uses: actions/setup-node@v4
|
|
20
|
-
with:
|
|
21
|
-
node-version: '20'
|
|
22
|
-
|
|
23
|
-
- name: Install dependencies
|
|
24
|
-
run: npm install
|
|
25
|
-
|
|
26
|
-
- name: Run tests
|
|
27
|
-
run: npm test
|
|
28
|
-
|
|
29
|
-
- name: Build package
|
|
30
|
-
run: npm run build
|
|
31
|
-
|
|
32
|
-
publish:
|
|
33
|
-
needs: build-and-test
|
|
34
|
-
runs-on: ubuntu-latest
|
|
35
|
-
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
|
36
|
-
permissions:
|
|
37
|
-
contents: read
|
|
38
|
-
|
|
39
|
-
steps:
|
|
40
|
-
- uses: actions/checkout@v4
|
|
41
|
-
with:
|
|
42
|
-
fetch-depth: 0
|
|
43
|
-
|
|
44
|
-
- name: Setup GitVersion
|
|
45
|
-
uses: gittools/actions/gitversion/setup@v0.10.2
|
|
46
|
-
with:
|
|
47
|
-
versionSpec: '5.x'
|
|
48
|
-
|
|
49
|
-
- name: Execute GitVersion
|
|
50
|
-
id: gitversion
|
|
51
|
-
uses: gittools/actions/gitversion/execute@v0.10.2
|
|
52
|
-
with:
|
|
53
|
-
useConfigFile: true
|
|
54
|
-
|
|
55
|
-
- name: Display GitVersion outputs
|
|
56
|
-
run: |
|
|
57
|
-
echo "FullSemVer: ${{ steps.gitversion.outputs.fullSemVer }}"
|
|
58
|
-
|
|
59
|
-
- uses: actions/setup-node@v4
|
|
60
|
-
with:
|
|
61
|
-
node-version: '20'
|
|
62
|
-
registry-url: 'https://registry.npmjs.org/'
|
|
63
|
-
|
|
64
|
-
- name: Install dependencies
|
|
65
|
-
run: npm install
|
|
66
|
-
|
|
67
|
-
- name: Build package
|
|
68
|
-
run: npm run build
|
|
69
|
-
|
|
70
|
-
- name: Update package version
|
|
71
|
-
run: npm version ${{ steps.gitversion.outputs.fullSemVer }} --no-git-tag-version
|
|
72
|
-
|
|
73
|
-
- name: Publish to npm
|
|
74
|
-
run: npm publish --access public
|
|
75
|
-
env:
|
|
76
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/GitVersion.yml
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["index.test.ts"],"names":[],"mappings":""}
|