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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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 CHANGED
@@ -1,7 +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';
1
+ export * from './types.js';
2
+ export * from './parser.js';
3
+ export * from './renderer.js';
4
+ export * from './lit-renderer.js';
5
+ export * from './pipeline.js';
6
+ export { LitRenderer as HTMLRenderer } from './lit-renderer.js';
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +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"}
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 CHANGED
@@ -1,6 +1,6 @@
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';
1
+ export * from './types.js';
2
+ export * from './parser.js';
3
+ export * from './renderer.js';
4
+ export * from './lit-renderer.js';
5
+ export * from './pipeline.js';
6
+ export { LitRenderer as HTMLRenderer } from './lit-renderer.js';
@@ -1,4 +1,4 @@
1
- import { ContentNode, MarkdownContent, PipelineConfig } from './types';
1
+ import { ContentNode, MarkdownContent, PipelineConfig } from './types.js';
2
2
  export declare class MarkdownPipeline {
3
3
  private parser;
4
4
  private renderer;
@@ -13,5 +13,6 @@ export declare class MarkdownPipeline {
13
13
  charset?: string;
14
14
  }): string;
15
15
  getConfig(): Readonly<Required<PipelineConfig>>;
16
+ getCustomCSS(): string;
16
17
  }
17
18
  //# sourceMappingURL=pipeline.d.ts.map
@@ -1 +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"}
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 CHANGED
@@ -1,5 +1,5 @@
1
- import { MarkdownParser } from './parser';
2
- import { HTMLRenderer } from './renderer';
1
+ import { MarkdownParser } from './parser.js';
2
+ import { HTMLRenderer } from './renderer.js';
3
3
  export class MarkdownPipeline {
4
4
  constructor(config = {}) {
5
5
  this.config = {
@@ -9,13 +9,18 @@ export class MarkdownPipeline {
9
9
  gfm: config.parseOptions?.gfm ?? true,
10
10
  breaks: config.parseOptions?.breaks ?? false,
11
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
12
17
  }
13
18
  };
14
19
  this.parser = new MarkdownParser({
15
20
  imagePathPrefix: this.config.imagePathPrefix,
16
21
  imageBaseUrl: this.config.imageBaseUrl
17
22
  });
18
- this.renderer = new HTMLRenderer();
23
+ this.renderer = new HTMLRenderer(this.config.styleOptions);
19
24
  }
20
25
  parse(markdown) {
21
26
  return this.parser.parseToNodes(markdown, this.config.parseOptions);
@@ -47,4 +52,7 @@ export class MarkdownPipeline {
47
52
  getConfig() {
48
53
  return { ...this.config };
49
54
  }
55
+ getCustomCSS() {
56
+ return this.renderer.getCustomCSS();
57
+ }
50
58
  }
@@ -1,8 +1,15 @@
1
- import { ContentNode } from './types';
1
+ import { ContentNode, StyleConfig } from './types.js';
2
2
  export declare class HTMLRenderer {
3
+ private config;
4
+ constructor(config?: StyleConfig);
5
+ private hasClassConfig;
6
+ private getClass;
7
+ private generateHeadingId;
8
+ private renderWithClass;
3
9
  renderNode(node: ContentNode): string;
4
10
  renderNodes(nodes: ContentNode[]): string;
5
11
  renderToHTMLString(nodes: ContentNode[]): string;
6
12
  render(markdown: string): string;
13
+ getCustomCSS(): string;
7
14
  }
8
15
  //# sourceMappingURL=renderer.d.ts.map
@@ -1 +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"}
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 CHANGED
@@ -1,35 +1,80 @@
1
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
+ }
2
36
  renderNode(node) {
3
37
  switch (node.type) {
4
38
  case 'heading':
5
39
  const level = node.attributes?.level || '2';
6
- return `<h${level}>${node.content || ''}</h${level}>`;
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}>`;
7
47
  case 'paragraph':
8
48
  if (node.children) {
9
- return `<p>${node.children.map(child => this.renderNode(child)).join('')}</p>`;
49
+ const childrenHtml = node.children.map(child => this.renderNode(child)).join('');
50
+ return this.renderWithClass('p', childrenHtml, 'paragraph');
10
51
  }
11
- return `<p>${node.content || ''}</p>`;
52
+ return this.renderWithClass('p', node.content || '', 'paragraph');
12
53
  case 'list':
13
54
  const tag = node.ordered ? 'ol' : 'ul';
14
55
  const items = node.children?.map(child => this.renderNode(child)).join('') || '';
15
- return `<${tag}>${items}</${tag}>`;
56
+ return this.renderWithClass(tag, items, 'list');
16
57
  case 'list-item':
17
- return `<li>${node.content || ''}</li>`;
58
+ return this.renderWithClass('li', node.content || '', 'list-item');
18
59
  case 'image':
19
60
  const src = node.src || node.attributes?.src || '';
20
61
  const alt = node.alt || node.attributes?.alt || '';
21
- return `<img src="${src}" alt="${alt}" class="${node.className || ''}">`;
62
+ const classStr = this.getClass('image', node.className || undefined);
63
+ return `<img src="${src}" alt="${alt}"${classStr ? ` class="${classStr}"` : ''}>`;
22
64
  case 'code':
23
- return `<pre><code class="language-${node.attributes?.lang || ''}">${node.content || ''}</code></pre>`;
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>`;
24
69
  case 'container':
25
70
  if (node.attributes?.tag === 'hr')
26
71
  return '<hr>';
27
72
  if (node.attributes?.tag === 'blockquote') {
28
73
  const children = node.children?.map(child => this.renderNode(child)).join('') || '';
29
- return `<blockquote>${children}</blockquote>`;
74
+ return this.renderWithClass('blockquote', children, 'blockquote');
30
75
  }
31
76
  const containerChildren = node.children?.map(child => this.renderNode(child)).join('') || '';
32
- return `<div class="${node.className || ''}">${containerChildren}</div>`;
77
+ return this.renderWithClass('div', containerChildren, 'container', node.className || undefined);
33
78
  case 'strong':
34
79
  return `<strong>${node.content || ''}</strong>`;
35
80
  case 'emphasis':
@@ -51,4 +96,7 @@ export class HTMLRenderer {
51
96
  render(markdown) {
52
97
  return markdown;
53
98
  }
99
+ getCustomCSS() {
100
+ return this.config.customCSS;
101
+ }
54
102
  }
package/dist/types.d.ts CHANGED
@@ -19,9 +19,15 @@ export interface ParseOptions {
19
19
  breaks?: boolean;
20
20
  pedantic?: boolean;
21
21
  }
22
+ export interface StyleConfig {
23
+ classPrefix?: string;
24
+ customCSS?: string;
25
+ addHeadingIds?: boolean;
26
+ }
22
27
  export interface PipelineConfig {
23
28
  imagePathPrefix?: string;
24
29
  imageBaseUrl?: string;
25
30
  parseOptions?: ParseOptions;
31
+ styleOptions?: StyleConfig;
26
32
  }
27
33
  //# sourceMappingURL=types.d.ts.map
@@ -1 +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"}
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leadertechie/md2html",
3
- "version": "0.1.0-alpha.2",
3
+ "version": "0.1.0-alpha.4",
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",
@@ -25,7 +25,7 @@
25
25
  "devDependencies": {
26
26
  "@types/node": "^20.0.0",
27
27
  "typescript": "^5.0.0",
28
- "vitest": "^2.0.0"
28
+ "vitest": "^3.0.0"
29
29
  },
30
30
  "keywords": [
31
31
  "markdown",