@leadertechie/md2html 0.1.0-alpha.0 → 0.1.0-alpha.2

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.
@@ -0,0 +1,7 @@
1
+ export * from './types';
2
+ export * from './parser';
3
+ export * from './renderer';
4
+ export * from './lit-renderer';
5
+ export * from './pipeline';
6
+ export { LitRenderer as HTMLRenderer } from './lit-renderer';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAE3B,OAAO,EAAE,WAAW,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
@@ -3,5 +3,4 @@ export * from './parser';
3
3
  export * from './renderer';
4
4
  export * from './lit-renderer';
5
5
  export * from './pipeline';
6
-
7
6
  export { LitRenderer as HTMLRenderer } from './lit-renderer';
@@ -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
+ }
@@ -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,17 @@
1
+ import { ContentNode, MarkdownContent, PipelineConfig } from './types';
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
+ }
17
+ //# 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,SAAS,CAAC;AAEvE,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,MAAM,CAA2B;gBAE7B,MAAM,GAAE,cAAmB;IAkBvC,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;CAGhD"}
@@ -0,0 +1,50 @@
1
+ import { MarkdownParser } from './parser';
2
+ import { HTMLRenderer } from './renderer';
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
+ };
14
+ this.parser = new MarkdownParser({
15
+ imagePathPrefix: this.config.imagePathPrefix,
16
+ imageBaseUrl: this.config.imageBaseUrl
17
+ });
18
+ this.renderer = new HTMLRenderer();
19
+ }
20
+ parse(markdown) {
21
+ return this.parser.parseToNodes(markdown, this.config.parseOptions);
22
+ }
23
+ parseWithMetadata(markdown) {
24
+ return this.parser.parse(markdown, this.config.parseOptions);
25
+ }
26
+ render(nodes) {
27
+ return this.renderer.renderNodes(nodes);
28
+ }
29
+ renderMarkdown(markdown) {
30
+ const nodes = this.parse(markdown);
31
+ return this.render(nodes);
32
+ }
33
+ renderPage(title, nodes, options) {
34
+ const html = this.render(nodes);
35
+ return `<!DOCTYPE html>
36
+ <html lang="${options?.lang || 'en'}">
37
+ <head>
38
+ <meta charset="${options?.charset || 'UTF-8'}">
39
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
40
+ <title>${title}</title>
41
+ </head>
42
+ <body>
43
+ ${html}
44
+ </body>
45
+ </html>`;
46
+ }
47
+ getConfig() {
48
+ return { ...this.config };
49
+ }
50
+ }
@@ -0,0 +1,8 @@
1
+ import { ContentNode } from './types';
2
+ export declare class HTMLRenderer {
3
+ renderNode(node: ContentNode): string;
4
+ renderNodes(nodes: ContentNode[]): string;
5
+ renderToHTMLString(nodes: ContentNode[]): string;
6
+ render(markdown: string): string;
7
+ }
8
+ //# 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,MAAM,SAAS,CAAC;AAEtC,qBAAa,YAAY;IACvB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM;IAiDrC,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;CAGjC"}
@@ -0,0 +1,54 @@
1
+ export class HTMLRenderer {
2
+ renderNode(node) {
3
+ switch (node.type) {
4
+ case 'heading':
5
+ const level = node.attributes?.level || '2';
6
+ return `<h${level}>${node.content || ''}</h${level}>`;
7
+ case 'paragraph':
8
+ if (node.children) {
9
+ return `<p>${node.children.map(child => this.renderNode(child)).join('')}</p>`;
10
+ }
11
+ return `<p>${node.content || ''}</p>`;
12
+ case 'list':
13
+ const tag = node.ordered ? 'ol' : 'ul';
14
+ const items = node.children?.map(child => this.renderNode(child)).join('') || '';
15
+ return `<${tag}>${items}</${tag}>`;
16
+ case 'list-item':
17
+ return `<li>${node.content || ''}</li>`;
18
+ case 'image':
19
+ const src = node.src || node.attributes?.src || '';
20
+ const alt = node.alt || node.attributes?.alt || '';
21
+ return `<img src="${src}" alt="${alt}" class="${node.className || ''}">`;
22
+ case 'code':
23
+ return `<pre><code class="language-${node.attributes?.lang || ''}">${node.content || ''}</code></pre>`;
24
+ case 'container':
25
+ if (node.attributes?.tag === 'hr')
26
+ return '<hr>';
27
+ if (node.attributes?.tag === 'blockquote') {
28
+ const children = node.children?.map(child => this.renderNode(child)).join('') || '';
29
+ return `<blockquote>${children}</blockquote>`;
30
+ }
31
+ const containerChildren = node.children?.map(child => this.renderNode(child)).join('') || '';
32
+ return `<div class="${node.className || ''}">${containerChildren}</div>`;
33
+ case 'strong':
34
+ return `<strong>${node.content || ''}</strong>`;
35
+ case 'emphasis':
36
+ return `<em>${node.content || ''}</em>`;
37
+ case 'text':
38
+ default:
39
+ return node.content || '';
40
+ }
41
+ }
42
+ renderNodes(nodes) {
43
+ if (!nodes || nodes.length === 0) {
44
+ return '';
45
+ }
46
+ return nodes.map(node => this.renderNode(node)).join('\n');
47
+ }
48
+ renderToHTMLString(nodes) {
49
+ return this.renderNodes(nodes);
50
+ }
51
+ render(markdown) {
52
+ return markdown;
53
+ }
54
+ }
@@ -0,0 +1,27 @@
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 PipelineConfig {
23
+ imagePathPrefix?: string;
24
+ imageBaseUrl?: string;
25
+ parseOptions?: ParseOptions;
26
+ }
27
+ //# 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,cAAc;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B"}
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.0",
3
+ "version": "0.1.0-alpha.2",
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,7 +0,0 @@
1
- next-version: 0.0.1
2
- mode: Mainline
3
- branches:
4
- main:
5
- regex: ^main$
6
- increment: Patch
7
- tag: alpha
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=index.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["index.test.ts"],"names":[],"mappings":""}
@@ -1,130 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { MarkdownParser } from '../src/parser';
3
- import { HTMLRenderer } from '../src/renderer';
4
- import { MarkdownPipeline } from '../src/pipeline';
5
- describe('MarkdownParser', () => {
6
- let parser;
7
- beforeEach(() => {
8
- parser = new MarkdownParser();
9
- });
10
- describe('parse', () => {
11
- it('should parse heading', () => {
12
- const result = parser.parse('# Hello World');
13
- expect(result.content).toHaveLength(1);
14
- expect(result.content[0].type).toBe('heading');
15
- expect(result.content[0].content).toBe('Hello World');
16
- });
17
- it('should parse multiple headings', () => {
18
- const result = parser.parse('# Heading 1\n## Heading 2\n### Heading 3');
19
- expect(result.content).toHaveLength(3);
20
- expect(result.content[0].type).toBe('heading');
21
- });
22
- it('should parse paragraph', () => {
23
- const result = parser.parse('This is a paragraph');
24
- expect(result.content).toHaveLength(1);
25
- expect(result.content[0].type).toBe('paragraph');
26
- });
27
- it('should parse list items', () => {
28
- const result = parser.parse('- Item 1\n- Item 2\n- Item 3');
29
- expect(result.content).toHaveLength(1);
30
- expect(result.content[0].type).toBe('list');
31
- expect(result.content[0].children).toHaveLength(3);
32
- });
33
- it('should parse image in paragraph', () => {
34
- const result = parser.parse('![Alt text](image.jpg)');
35
- expect(result.content).toHaveLength(1);
36
- expect(result.content[0].type).toBe('paragraph');
37
- expect(result.content[0].children?.[0].type).toBe('image');
38
- });
39
- it('should parse code block', () => {
40
- const result = parser.parse('```javascript\nconst x = 1;\n```');
41
- expect(result.content).toHaveLength(1);
42
- expect(result.content[0].type).toBe('code');
43
- });
44
- it('should handle image path prefix', () => {
45
- const parserWithPrefix = new MarkdownParser({ imagePathPrefix: 'images/' });
46
- const result = parserWithPrefix.parse('![Alt](photo.jpg)');
47
- expect(result.content[0].children?.[0].src).toBe('images/photo.jpg');
48
- });
49
- it('should handle imageBaseUrl', () => {
50
- const parserWithBaseUrl = new MarkdownParser({ imageBaseUrl: 'https://cdn.example.com/' });
51
- const result = parserWithBaseUrl.parse('![Alt](photo.jpg)');
52
- expect(result.content[0].children?.[0].src).toBe('https://cdn.example.com/photo.jpg');
53
- });
54
- });
55
- });
56
- describe('HTMLRenderer', () => {
57
- let renderer;
58
- beforeEach(() => {
59
- renderer = new HTMLRenderer();
60
- });
61
- describe('renderNode', () => {
62
- it('should render heading', () => {
63
- const node = { type: 'heading', content: 'Hello', attributes: { level: '1' } };
64
- const html = renderer.renderNode(node);
65
- expect(html).toBe('<h1>Hello</h1>');
66
- });
67
- it('should render paragraph', () => {
68
- const node = { type: 'paragraph', content: 'Hello world' };
69
- const html = renderer.renderNode(node);
70
- expect(html).toBe('<p>Hello world</p>');
71
- });
72
- it('should render image', () => {
73
- const node = { type: 'image', src: 'image.jpg', alt: 'Alt text' };
74
- const html = renderer.renderNode(node);
75
- expect(html).toBe('<img src="image.jpg" alt="Alt text" class="">');
76
- });
77
- });
78
- describe('renderNodes', () => {
79
- it('should render multiple nodes', () => {
80
- const nodes = [
81
- { type: 'heading', content: 'Title', attributes: { level: '1' } },
82
- { type: 'paragraph', content: 'Content' }
83
- ];
84
- const html = renderer.renderNodes(nodes);
85
- expect(html).toContain('<h1>Title</h1>');
86
- expect(html).toContain('<p>Content</p>');
87
- });
88
- it('should handle empty array', () => {
89
- const html = renderer.renderNodes([]);
90
- expect(html).toBe('');
91
- });
92
- });
93
- });
94
- describe('MarkdownPipeline', () => {
95
- let pipeline;
96
- beforeEach(() => {
97
- pipeline = new MarkdownPipeline();
98
- });
99
- describe('parse', () => {
100
- it('should parse markdown to nodes', () => {
101
- const nodes = pipeline.parse('# Hello');
102
- expect(nodes).toHaveLength(1);
103
- expect(nodes[0].type).toBe('heading');
104
- });
105
- });
106
- describe('renderMarkdown', () => {
107
- it('should parse and render in one call', () => {
108
- const html = pipeline.renderMarkdown('# Hello World\n\nThis is a paragraph.');
109
- expect(html).toContain('<h1>Hello World</h1>');
110
- expect(html).toContain('<p>This is a paragraph.</p>');
111
- });
112
- });
113
- describe('renderPage', () => {
114
- it('should render full HTML page', () => {
115
- const nodes = [
116
- { type: 'heading', content: 'Title', attributes: { level: '1' } }
117
- ];
118
- const page = pipeline.renderPage('My Page', nodes);
119
- expect(page).toContain('<!DOCTYPE html>');
120
- expect(page).toContain('<title>My Page</title>');
121
- });
122
- });
123
- describe('configuration', () => {
124
- it('should use imagePathPrefix config', () => {
125
- const pipelineWithConfig = new MarkdownPipeline({ imagePathPrefix: 'images/' });
126
- const nodes = pipelineWithConfig.parse('![img](test.jpg)');
127
- expect(nodes[0].children?.[0].src).toBe('images/test.jpg');
128
- });
129
- });
130
- });
@@ -1,155 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { MarkdownParser } from '../src/parser';
3
- import { HTMLRenderer } from '../src/renderer';
4
- import { LitRenderer } from '../src/lit-renderer';
5
- import { MarkdownPipeline } from '../src/pipeline';
6
- import type { ContentNode } from '../src/types';
7
-
8
- describe('MarkdownParser', () => {
9
- let parser: MarkdownParser;
10
-
11
- beforeEach(() => {
12
- parser = new MarkdownParser();
13
- });
14
-
15
- describe('parse', () => {
16
- it('should parse heading', () => {
17
- const result = parser.parse('# Hello World');
18
- expect(result.content).toHaveLength(1);
19
- expect(result.content[0].type).toBe('heading');
20
- expect(result.content[0].content).toBe('Hello World');
21
- });
22
-
23
- it('should parse multiple headings', () => {
24
- const result = parser.parse('# Heading 1\n## Heading 2\n### Heading 3');
25
- expect(result.content).toHaveLength(3);
26
- expect(result.content[0].type).toBe('heading');
27
- });
28
-
29
- it('should parse paragraph', () => {
30
- const result = parser.parse('This is a paragraph');
31
- expect(result.content).toHaveLength(1);
32
- expect(result.content[0].type).toBe('paragraph');
33
- });
34
-
35
- it('should parse list items', () => {
36
- const result = parser.parse('- Item 1\n- Item 2\n- Item 3');
37
- expect(result.content).toHaveLength(1);
38
- expect(result.content[0].type).toBe('list');
39
- expect(result.content[0].children).toHaveLength(3);
40
- });
41
-
42
- it('should parse image in paragraph', () => {
43
- const result = parser.parse('![Alt text](image.jpg)');
44
- expect(result.content).toHaveLength(1);
45
- expect(result.content[0].type).toBe('paragraph');
46
- expect(result.content[0].children?.[0].type).toBe('image');
47
- });
48
-
49
- it('should parse code block', () => {
50
- const result = parser.parse('```javascript\nconst x = 1;\n```');
51
- expect(result.content).toHaveLength(1);
52
- expect(result.content[0].type).toBe('code');
53
- });
54
-
55
- it('should handle image path prefix', () => {
56
- const parserWithPrefix = new MarkdownParser({ imagePathPrefix: 'images/' });
57
- const result = parserWithPrefix.parse('![Alt](photo.jpg)');
58
- expect(result.content[0].children?.[0].src).toBe('images/photo.jpg');
59
- });
60
-
61
- it('should handle imageBaseUrl', () => {
62
- const parserWithBaseUrl = new MarkdownParser({ imageBaseUrl: 'https://cdn.example.com/' });
63
- const result = parserWithBaseUrl.parse('![Alt](photo.jpg)');
64
- expect(result.content[0].children?.[0].src).toBe('https://cdn.example.com/photo.jpg');
65
- });
66
- });
67
- });
68
-
69
- describe('HTMLRenderer', () => {
70
- let renderer: HTMLRenderer;
71
-
72
- beforeEach(() => {
73
- renderer = new HTMLRenderer();
74
- });
75
-
76
- describe('renderNode', () => {
77
- it('should render heading', () => {
78
- const node: ContentNode = { type: 'heading', content: 'Hello', attributes: { level: '1' } };
79
- const html = renderer.renderNode(node);
80
- expect(html).toBe('<h1>Hello</h1>');
81
- });
82
-
83
- it('should render paragraph', () => {
84
- const node: ContentNode = { type: 'paragraph', content: 'Hello world' };
85
- const html = renderer.renderNode(node);
86
- expect(html).toBe('<p>Hello world</p>');
87
- });
88
-
89
- it('should render image', () => {
90
- const node: ContentNode = { type: 'image', src: 'image.jpg', alt: 'Alt text' };
91
- const html = renderer.renderNode(node);
92
- expect(html).toBe('<img src="image.jpg" alt="Alt text" class="">');
93
- });
94
- });
95
-
96
- describe('renderNodes', () => {
97
- it('should render multiple nodes', () => {
98
- const nodes: ContentNode[] = [
99
- { type: 'heading', content: 'Title', attributes: { level: '1' } },
100
- { type: 'paragraph', content: 'Content' }
101
- ];
102
- const html = renderer.renderNodes(nodes);
103
- expect(html).toContain('<h1>Title</h1>');
104
- expect(html).toContain('<p>Content</p>');
105
- });
106
-
107
- it('should handle empty array', () => {
108
- const html = renderer.renderNodes([]);
109
- expect(html).toBe('');
110
- });
111
- });
112
- });
113
-
114
- describe('MarkdownPipeline', () => {
115
- let pipeline: MarkdownPipeline;
116
-
117
- beforeEach(() => {
118
- pipeline = new MarkdownPipeline();
119
- });
120
-
121
- describe('parse', () => {
122
- it('should parse markdown to nodes', () => {
123
- const nodes = pipeline.parse('# Hello');
124
- expect(nodes).toHaveLength(1);
125
- expect(nodes[0].type).toBe('heading');
126
- });
127
- });
128
-
129
- describe('renderMarkdown', () => {
130
- it('should parse and render in one call', () => {
131
- const html = pipeline.renderMarkdown('# Hello World\n\nThis is a paragraph.');
132
- expect(html).toContain('<h1>Hello World</h1>');
133
- expect(html).toContain('<p>This is a paragraph.</p>');
134
- });
135
- });
136
-
137
- describe('renderPage', () => {
138
- it('should render full HTML page', () => {
139
- const nodes: ContentNode[] = [
140
- { type: 'heading', content: 'Title', attributes: { level: '1' } }
141
- ];
142
- const page = pipeline.renderPage('My Page', nodes);
143
- expect(page).toContain('<!DOCTYPE html>');
144
- expect(page).toContain('<title>My Page</title>');
145
- });
146
- });
147
-
148
- describe('configuration', () => {
149
- it('should use imagePathPrefix config', () => {
150
- const pipelineWithConfig = new MarkdownPipeline({ imagePathPrefix: 'images/' });
151
- const nodes = pipelineWithConfig.parse('![img](test.jpg)');
152
- expect(nodes[0].children?.[0].src).toBe('images/test.jpg');
153
- });
154
- });
155
- });
@@ -1,91 +0,0 @@
1
- import { TemplateResult, html } from 'lit';
2
- import { unsafeHTML } from 'lit/directives/unsafe-html.js';
3
- import { ContentNode } from './types';
4
-
5
- export class LitRenderer {
6
- private renderTextNode(node: ContentNode): TemplateResult {
7
- if (node.type === 'image') {
8
- return html`<img src="${node.src}" alt="${node.alt || ''}" class="inline-image" style="max-width:100%;height:auto;">`;
9
- }
10
- return html`${unsafeHTML(node.content || '')}`;
11
- }
12
-
13
- renderNode(node: ContentNode): TemplateResult {
14
- switch (node.type) {
15
- case 'heading': {
16
- const level = node.attributes?.level || '2';
17
- if (level === '1') return html`<h1>${unsafeHTML(node.content)}</h1>`;
18
- if (level === '2') return html`<h2>${unsafeHTML(node.content)}</h2>`;
19
- if (level === '3') return html`<h3>${unsafeHTML(node.content)}</h3>`;
20
- return html`<h2>${unsafeHTML(node.content)}</h2>`;
21
- }
22
-
23
- case 'paragraph':
24
- if (node.children) {
25
- return html`<p>${node.children.map(child => this.renderTextNode(child))}</p>`;
26
- }
27
- return html`<p>${unsafeHTML(node.content)}</p>`;
28
-
29
- case 'list':
30
- return html`<ul>${node.children?.map(child => this.renderNode(child))}</ul>`;
31
-
32
- case 'list-item':
33
- return html`<li>${unsafeHTML(node.content)}</li>`;
34
-
35
- case 'image':
36
- return html`<img src="${node.src || node.attributes?.src}" alt="${node.alt || node.attributes?.alt || ''}" class="${node.className || ''}" style="max-width:100%;height:auto;">`;
37
-
38
- case 'container':
39
- return html`<div class="${node.className || ''}" style="${node.attributes?.style || ''}">
40
- ${node.children?.map(child => this.renderNode(child))}
41
- </div>`;
42
-
43
- case 'code':
44
- return html`<pre><code class="language-${node.attributes?.lang || ''}">${node.content || ''}</code></pre>`;
45
-
46
- case 'text':
47
- return html`${node.content}`;
48
-
49
- default:
50
- return html``;
51
- }
52
- }
53
-
54
- renderNodes(nodes: ContentNode[]): TemplateResult {
55
- if (!nodes || nodes.length === 0) {
56
- return html``;
57
- }
58
- return html`${nodes.map(node => this.renderNode(node))}`;
59
- }
60
-
61
- renderToHTMLString(nodes: ContentNode[]): string {
62
- if (!nodes || nodes.length === 0) {
63
- return '';
64
- }
65
- return nodes.map(node => this.nodeToHTMLString(node)).join('\n');
66
- }
67
-
68
- private nodeToHTMLString(node: ContentNode): string {
69
- switch (node.type) {
70
- case 'heading':
71
- const level = node.attributes?.level || '2';
72
- return `<h${level}>${node.content}</h${level}>`;
73
- case 'paragraph':
74
- return `<p>${node.content}</p>`;
75
- case 'list':
76
- const items = node.children?.map(child => this.nodeToHTMLString(child)).join('') || '';
77
- return `<ul>${items}</ul>`;
78
- case 'list-item':
79
- return `<li>${node.content}</li>`;
80
- case 'image':
81
- return `<img src="${node.attributes?.src}" alt="${node.attributes?.alt}" class="${node.className || ''}">`;
82
- case 'container':
83
- const childrenHTML = node.children?.map(child => this.nodeToHTMLString(child)).join('') || '';
84
- return `<div class="${node.className || ''}">${childrenHTML}</div>`;
85
- case 'text':
86
- return node.content || '';
87
- default:
88
- return '';
89
- }
90
- }
91
- }
package/src/parser.ts DELETED
@@ -1,142 +0,0 @@
1
- import { marked } from 'marked';
2
- import { ContentNode, MarkdownContent, ParseOptions } from './types';
3
-
4
- export class MarkdownParser {
5
- private imagePathPrefix: string;
6
- private imageBaseUrl: string;
7
-
8
- constructor(options?: { imagePathPrefix?: string; imageBaseUrl?: string }) {
9
- this.imagePathPrefix = options?.imagePathPrefix || '';
10
- this.imageBaseUrl = options?.imageBaseUrl || '';
11
- }
12
-
13
- private processImagePath(src: string): string {
14
- if (src.startsWith('http') || src.startsWith('/')) {
15
- return src;
16
- }
17
- let path = this.imagePathPrefix ? `${this.imagePathPrefix}${src}` : src;
18
- if (this.imageBaseUrl && !path.startsWith('http')) {
19
- path = `${this.imageBaseUrl}${path}`;
20
- }
21
- return path;
22
- }
23
-
24
- private processInlineFormatting(text: string): string {
25
- return text
26
- .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
27
- .replace(/\*(.+?)\*/g, '<em>$1</em>');
28
- }
29
-
30
- private parseTokens(tokens: unknown[]): ContentNode[] {
31
- const nodes: ContentNode[] = [];
32
-
33
- for (const token of tokens) {
34
- const node = this.parseToken(token as Record<string, unknown>);
35
- if (node) {
36
- nodes.push(node);
37
- }
38
- }
39
-
40
- return nodes;
41
- }
42
-
43
- private parseToken(token: Record<string, unknown>): ContentNode | null {
44
- switch (token.type) {
45
- case 'heading':
46
- return {
47
- type: 'heading',
48
- content: token.text as string,
49
- attributes: { level: String(token.depth) }
50
- };
51
-
52
- case 'paragraph':
53
- const tokens = (token.tokens as Array<Record<string, unknown>>) || [];
54
- const hasInlineImage = tokens.some(t => t.type === 'image');
55
-
56
- if (hasInlineImage) {
57
- const children = tokens.map(t => {
58
- if (t.type === 'image') {
59
- return {
60
- type: 'image' as const,
61
- src: this.processImagePath(t.href as string),
62
- alt: t.text as string || ''
63
- };
64
- }
65
- return {
66
- type: 'text' as const,
67
- content: this.processInlineFormatting(t.text as string || '')
68
- };
69
- });
70
- return {
71
- type: 'paragraph',
72
- children
73
- };
74
- }
75
-
76
- return {
77
- type: 'paragraph',
78
- content: this.processInlineFormatting(token.text as string)
79
- };
80
-
81
- case 'list':
82
- return {
83
- type: 'list',
84
- ordered: token.ordered as boolean,
85
- children: (token.items as Array<Record<string, unknown>>).map((item) => ({
86
- type: 'list-item',
87
- content: this.processInlineFormatting(item.text as string)
88
- }))
89
- };
90
-
91
- case 'image':
92
- return {
93
- type: 'image',
94
- src: this.processImagePath(token.href as string),
95
- alt: token.title as string || ''
96
- };
97
-
98
- case 'code':
99
- return {
100
- type: 'code',
101
- content: token.text as string,
102
- attributes: { lang: token.lang as string || '' }
103
- };
104
-
105
- case 'hr':
106
- return { type: 'container', attributes: { tag: 'hr' } };
107
-
108
- case 'blockquote':
109
- return {
110
- type: 'container',
111
- attributes: { tag: 'blockquote' },
112
- children: this.parseTokens((token as Record<string, unknown>).tokens as unknown[] || [])
113
- };
114
-
115
- case 'html':
116
- return { type: 'container', content: token.raw as string };
117
-
118
- default:
119
- return null;
120
- }
121
- }
122
-
123
- parse(markdown: string, options?: ParseOptions): MarkdownContent {
124
- const parseOptions = {
125
- gfm: options?.gfm ?? true,
126
- breaks: options?.breaks ?? false,
127
- pedantic: options?.pedantic ?? false
128
- };
129
-
130
- const tokens = marked.lexer(markdown, parseOptions as Parameters<typeof marked.lexer>[1]);
131
- const content = this.parseTokens(tokens);
132
-
133
- return {
134
- title: '',
135
- content
136
- };
137
- }
138
-
139
- parseToNodes(markdown: string, options?: ParseOptions): ContentNode[] {
140
- return this.parse(markdown, options).content;
141
- }
142
- }
package/src/pipeline.ts DELETED
@@ -1,66 +0,0 @@
1
- import { MarkdownParser } from './parser';
2
- import { HTMLRenderer } from './renderer';
3
- import { ContentNode, MarkdownContent, PipelineConfig } from './types';
4
-
5
- export class MarkdownPipeline {
6
- private parser: MarkdownParser;
7
- private renderer: HTMLRenderer;
8
- private config: Required<PipelineConfig>;
9
-
10
- constructor(config: PipelineConfig = {}) {
11
- this.config = {
12
- imagePathPrefix: config.imagePathPrefix || '',
13
- imageBaseUrl: config.imageBaseUrl || '',
14
- parseOptions: {
15
- gfm: config.parseOptions?.gfm ?? true,
16
- breaks: config.parseOptions?.breaks ?? false,
17
- pedantic: config.parseOptions?.pedantic ?? false
18
- }
19
- };
20
-
21
- this.parser = new MarkdownParser({
22
- imagePathPrefix: this.config.imagePathPrefix,
23
- imageBaseUrl: this.config.imageBaseUrl
24
- });
25
- this.renderer = new HTMLRenderer();
26
- }
27
-
28
- parse(markdown: string): ContentNode[] {
29
- return this.parser.parseToNodes(markdown, this.config.parseOptions);
30
- }
31
-
32
- parseWithMetadata(markdown: string): MarkdownContent {
33
- return this.parser.parse(markdown, this.config.parseOptions);
34
- }
35
-
36
- render(nodes: ContentNode[]): string {
37
- return this.renderer.renderNodes(nodes);
38
- }
39
-
40
- renderMarkdown(markdown: string): string {
41
- const nodes = this.parse(markdown);
42
- return this.render(nodes);
43
- }
44
-
45
- renderPage(title: string, nodes: ContentNode[], options?: {
46
- lang?: string;
47
- charset?: string;
48
- }): string {
49
- const html = this.render(nodes);
50
- return `<!DOCTYPE html>
51
- <html lang="${options?.lang || 'en'}">
52
- <head>
53
- <meta charset="${options?.charset || 'UTF-8'}">
54
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
55
- <title>${title}</title>
56
- </head>
57
- <body>
58
- ${html}
59
- </body>
60
- </html>`;
61
- }
62
-
63
- getConfig(): Readonly<Required<PipelineConfig>> {
64
- return { ...this.config };
65
- }
66
- }
package/src/renderer.ts DELETED
@@ -1,67 +0,0 @@
1
- import { ContentNode } from './types';
2
-
3
- export class HTMLRenderer {
4
- renderNode(node: ContentNode): string {
5
- switch (node.type) {
6
- case 'heading':
7
- const level = node.attributes?.level || '2';
8
- return `<h${level}>${node.content || ''}</h${level}>`;
9
-
10
- case 'paragraph':
11
- if (node.children) {
12
- return `<p>${node.children.map(child => this.renderNode(child)).join('')}</p>`;
13
- }
14
- return `<p>${node.content || ''}</p>`;
15
-
16
- case 'list':
17
- const tag = node.ordered ? 'ol' : 'ul';
18
- const items = node.children?.map(child => this.renderNode(child)).join('') || '';
19
- return `<${tag}>${items}</${tag}>`;
20
-
21
- case 'list-item':
22
- return `<li>${node.content || ''}</li>`;
23
-
24
- case 'image':
25
- const src = node.src || node.attributes?.src || '';
26
- const alt = node.alt || node.attributes?.alt || '';
27
- return `<img src="${src}" alt="${alt}" class="${node.className || ''}">`;
28
-
29
- case 'code':
30
- return `<pre><code class="language-${node.attributes?.lang || ''}">${node.content || ''}</code></pre>`;
31
-
32
- case 'container':
33
- if (node.attributes?.tag === 'hr') return '<hr>';
34
- if (node.attributes?.tag === 'blockquote') {
35
- const children = node.children?.map(child => this.renderNode(child)).join('') || '';
36
- return `<blockquote>${children}</blockquote>`;
37
- }
38
- const containerChildren = node.children?.map(child => this.renderNode(child)).join('') || '';
39
- return `<div class="${node.className || ''}">${containerChildren}</div>`;
40
-
41
- case 'strong':
42
- return `<strong>${node.content || ''}</strong>`;
43
-
44
- case 'emphasis':
45
- return `<em>${node.content || ''}</em>`;
46
-
47
- case 'text':
48
- default:
49
- return node.content || '';
50
- }
51
- }
52
-
53
- renderNodes(nodes: ContentNode[]): string {
54
- if (!nodes || nodes.length === 0) {
55
- return '';
56
- }
57
- return nodes.map(node => this.renderNode(node)).join('\n');
58
- }
59
-
60
- renderToHTMLString(nodes: ContentNode[]): string {
61
- return this.renderNodes(nodes);
62
- }
63
-
64
- render(markdown: string): string {
65
- return markdown;
66
- }
67
- }
package/src/types.ts DELETED
@@ -1,40 +0,0 @@
1
- export type ContentNodeType =
2
- | 'text'
3
- | 'heading'
4
- | 'paragraph'
5
- | 'list'
6
- | 'list-item'
7
- | 'image'
8
- | 'code'
9
- | 'container'
10
- | 'strong'
11
- | 'emphasis';
12
-
13
- export interface ContentNode {
14
- type: ContentNodeType;
15
- content?: string;
16
- children?: ContentNode[];
17
- attributes?: Record<string, unknown>;
18
- className?: string;
19
- src?: string;
20
- alt?: string;
21
- ordered?: boolean;
22
- }
23
-
24
- export interface MarkdownContent {
25
- title: string;
26
- metadata?: Record<string, unknown>;
27
- content: ContentNode[];
28
- }
29
-
30
- export interface ParseOptions {
31
- gfm?: boolean;
32
- breaks?: boolean;
33
- pedantic?: boolean;
34
- }
35
-
36
- export interface PipelineConfig {
37
- imagePathPrefix?: string;
38
- imageBaseUrl?: string;
39
- parseOptions?: ParseOptions;
40
- }
package/tsconfig.json DELETED
@@ -1,18 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "ESNext",
5
- "lib": ["ES2020"],
6
- "moduleResolution": "bundler",
7
- "strict": true,
8
- "esModuleInterop": true,
9
- "skipLibCheck": true,
10
- "forceConsistentCasingInFileNames": true,
11
- "declaration": true,
12
- "declarationMap": true,
13
- "outDir": "./dist",
14
- "rootDir": "./src"
15
- },
16
- "include": ["src/**/*"],
17
- "exclude": ["node_modules", "dist", "__tests__", "**/*.test.ts"]
18
- }