@sullux/markdown-html 1.0.0 → 1.0.1
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
|
@@ -1,113 +1,45 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/logo.svg" alt="Markdown HTML" width="160" height="160" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# @sullux/markdown-html
|
|
2
6
|
|
|
3
|
-
A high-performance, bidirectional Markdown
|
|
7
|
+
A high-performance, zero-dependency, bidirectional Markdown ↔ HTML compiler supporting GitHub-Flavored Markdown (GFM), GitBook hints, pluggable code block renderers, built-in syntax highlighting, and custom image dimensions.
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
📖 **Official Documentation:** [https://sullux.com/projects/markdown/markdown-html/](https://sullux.com/projects/markdown/markdown-html/)
|
|
6
10
|
|
|
7
|
-
##
|
|
11
|
+
## Overview
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
* `markdownToHtml(markdown, options)`: Renders AST-driven semantic HTML.
|
|
11
|
-
* `htmlToMarkdown(html)`: Converts HTML documents/emails back to clean GFM Markdown.
|
|
12
|
-
* **Pluggable Code Block Renderers:** Register custom handlers for languages like `mermaid`, `dot`, or bespoke blocks via `options.codeRenderers`.
|
|
13
|
-
* **Built-in Zero-Dependency Syntax Highlighting:**
|
|
14
|
-
* Out-of-the-box tokenizers for `js`, `json`, `yaml`, `bash`, `html`, and `sql`.
|
|
15
|
-
* Customizable and overridable via `options.tokenizers`.
|
|
16
|
-
* **Image Dimension Styling:** Multi-syntax dimension support (Obsidian `|400x200`, Pandoc/GitLab `{width=50%}`, GitHub `{:width="400px"}`, VS Code `=300x200`) rendered as inline CSS (`style="width: 400px; height: 200px;"`).
|
|
17
|
-
* **Rich Component Support:**
|
|
18
|
-
* Auto-slugified heading IDs with deduplication (`<h2 id="section-one-1">`).
|
|
19
|
-
* Callouts (`> [!NOTE]`, `{% hint %}`).
|
|
20
|
-
* Task list checkboxes (`- [ ]`, `- [x]`).
|
|
21
|
-
* GFM tables with column alignment attributes (`<th align="center">`).
|
|
22
|
-
* YAML frontmatter stripping.
|
|
13
|
+
Part of the Sullux markdown suite, `@sullux/markdown-html` relies on `@sullux/markdown-compiler` to deliver fast, bidirectional conversion between Markdown and clean, semantic HTML. It transforms Markdown ASTs into HTML with auto-slugified heading IDs, callout boxes, task lists, GFM tables, and multi-syntax image dimensions styled as inline CSS. It also parses arbitrary HTML documents, emails, or CMS content back into canonical GFM Markdown with style normalization and layout table flattening.
|
|
23
14
|
|
|
24
|
-
|
|
15
|
+
Key capabilities include pluggable code block renderers for diagrams (such as Mermaid or Graphviz) and zero-dependency compile-time syntax highlighting for common languages (`js`, `json`, `yaml`, `bash`, `html`, `sql`).
|
|
25
16
|
|
|
26
|
-
|
|
27
|
-
packages/markdown-html/
|
|
28
|
-
├── lib/
|
|
29
|
-
│ ├── markdown-to-html/ # Markdown -> AST -> HTML renderer (<100 lines each)
|
|
30
|
-
│ │ ├── index.js
|
|
31
|
-
│ │ ├── render-block.js
|
|
32
|
-
│ │ ├── render-inline.js
|
|
33
|
-
│ │ ├── slugify.js
|
|
34
|
-
│ │ ├── escape.js
|
|
35
|
-
│ │ └── highlight/ # Zero-dependency language tokenizers
|
|
36
|
-
│ │ ├── index.js
|
|
37
|
-
│ │ ├── js.js
|
|
38
|
-
│ │ ├── json.js
|
|
39
|
-
│ │ ├── yaml.js
|
|
40
|
-
│ │ ├── bash.js
|
|
41
|
-
│ │ ├── html.js
|
|
42
|
-
│ │ └── sql.js
|
|
43
|
-
│ └── html-to-markdown/ # HTML -> AST -> Markdown compiler (<100 lines each)
|
|
44
|
-
│ ├── index.js
|
|
45
|
-
│ ├── parse-html.js
|
|
46
|
-
│ ├── ast-builder.js
|
|
47
|
-
│ ├── block-builder.js
|
|
48
|
-
│ ├── block-table.js
|
|
49
|
-
│ ├── inline-builder.js
|
|
50
|
-
│ └── utils.js
|
|
51
|
-
├── index.js # Package entrypoint
|
|
52
|
-
└── package.json # Package manifest
|
|
53
|
-
```
|
|
17
|
+
## Quick Start
|
|
54
18
|
|
|
55
|
-
|
|
19
|
+
### Installation
|
|
56
20
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
```
|
|
60
|
-
const { markdownToHtml } = require('@sullux/markdown-html')
|
|
61
|
-
|
|
62
|
-
const md = `
|
|
63
|
-
# System Spec
|
|
64
|
-
|
|
65
|
-

|
|
21
|
+
```bash
|
|
22
|
+
yarn add @sullux/markdown-html
|
|
23
|
+
```
|
|
66
24
|
|
|
67
|
-
|
|
68
|
-
const name = "Sullux";
|
|
69
|
-
\`\`\`
|
|
25
|
+
### Usage
|
|
70
26
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
A-->B;
|
|
74
|
-
\`\`\`
|
|
75
|
-
`
|
|
27
|
+
```javascript
|
|
28
|
+
const { markdownToHtml, htmlToMarkdown } = require('@sullux/markdown-html')
|
|
76
29
|
|
|
77
|
-
|
|
30
|
+
// Convert Markdown to semantic HTML with custom code renderers
|
|
31
|
+
const html = markdownToHtml('# Hello\n\n', {
|
|
78
32
|
codeRenderers: {
|
|
79
33
|
mermaid: (node) => `<div class="mermaid">${node.value}</div>\n`,
|
|
80
34
|
},
|
|
81
35
|
})
|
|
82
36
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
<h1 id="system-spec">System Spec</h1>
|
|
86
|
-
<p><img src="schema.png" alt="Diagram" style="width: 400px; height: 200px;" /></p>
|
|
87
|
-
<pre><code class="language-js"><span class="hl-kw">const</span> <span class="hl-id">name</span> <span class="hl-punc">=</span> <span class="hl-str">"Sullux"</span><span class="hl-punc">;</span></code></pre>
|
|
88
|
-
<div class="mermaid">graph TD;
|
|
89
|
-
A-->B;</div>
|
|
90
|
-
*/
|
|
37
|
+
// Convert HTML back to clean GFM Markdown
|
|
38
|
+
const markdown = htmlToMarkdown('<h1>Hello</h1><p>This is <strong>bold</strong> text.</p>')
|
|
91
39
|
```
|
|
92
40
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
```javascript
|
|
96
|
-
const { htmlToMarkdown } = require('@sullux/markdown-html')
|
|
41
|
+
For complete guides, syntax highlighting details, and full API specifications, see the [official documentation](https://sullux.com/projects/markdown/markdown-html/).
|
|
97
42
|
|
|
98
|
-
|
|
99
|
-
const markdown = htmlToMarkdown(htmlInput)
|
|
43
|
+
## Contributing & License
|
|
100
44
|
|
|
101
|
-
|
|
102
|
-
/* Output:
|
|
103
|
-
# Title
|
|
104
|
-
|
|
105
|
-
This is **bold** text with [a link](https://sullux.com).
|
|
106
|
-
*/
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
## Running Unit Tests
|
|
110
|
-
|
|
111
|
-
```bash
|
|
112
|
-
yarn test
|
|
113
|
-
```
|
|
45
|
+
Please see the [Monorepo README](../../README.md) for contribution guidelines, testing instructions, and license details.
|
|
@@ -21,6 +21,13 @@ const processBlockNode = (node, style, blocks, currentInline, traverse, toAst) =
|
|
|
21
21
|
return true
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
if ((attrs.class || '').includes('math-display') || (tagName !== 'span' && attrs['data-latex'])) {
|
|
25
|
+
flushInline()
|
|
26
|
+
const latex = attrs['data-latex'] || (node.children ? node.children.map((c) => c.value || '').join('').replace(/^\$\$|\$\$$/g, '').trim() : '')
|
|
27
|
+
blocks.push({ type: 'mathBlock', value: latex })
|
|
28
|
+
return true
|
|
29
|
+
}
|
|
30
|
+
|
|
24
31
|
const headerMatch = tagName.match(/^h([1-6])$/)
|
|
25
32
|
if (headerMatch) {
|
|
26
33
|
flushInline()
|
|
@@ -47,6 +47,11 @@ const processInlineNode = (node, style, currentInline, traverse) => {
|
|
|
47
47
|
if (attrs.src) currentInline.push({ type: 'image', url: attrs.src, alt: attrs.alt || '' })
|
|
48
48
|
return true
|
|
49
49
|
}
|
|
50
|
+
if ((attrs.class || '').includes('math-inline') || (tagName === 'span' && attrs['data-latex'])) {
|
|
51
|
+
const latex = attrs['data-latex'] || (node.children ? node.children.map((c) => c.value || '').join('').replace(/^\$|\$$/g, '').trim() : '')
|
|
52
|
+
currentInline.push({ type: 'inlineMath', value: latex })
|
|
53
|
+
return true
|
|
54
|
+
}
|
|
50
55
|
if (tagName === 'a') {
|
|
51
56
|
const childTokens = []
|
|
52
57
|
for (const child of node.children) traverse(child, nextStyle, childTokens)
|
|
@@ -8,13 +8,13 @@ const renderBlock = (node, options = {}) => {
|
|
|
8
8
|
|
|
9
9
|
switch (node.type) {
|
|
10
10
|
case 'header': {
|
|
11
|
-
const text = renderInline(node.children)
|
|
11
|
+
const text = renderInline(node.children, options)
|
|
12
12
|
const rawText = node.children ? node.children.map((c) => (c.type === 'text' ? c.value : '')).join('') : ''
|
|
13
13
|
const slug = (options.slugify || slugify)(rawText, options.usedSlugs)
|
|
14
14
|
return `<h${node.level} id="${slug}">${text}</h${node.level}>\n`
|
|
15
15
|
}
|
|
16
16
|
case 'paragraph': {
|
|
17
|
-
return `<p>${renderInline(node.children)}</p>\n`
|
|
17
|
+
return `<p>${renderInline(node.children, options)}</p>\n`
|
|
18
18
|
}
|
|
19
19
|
case 'codeBlock': {
|
|
20
20
|
const lang = node.language || ''
|
|
@@ -36,11 +36,11 @@ const renderBlock = (node, options = {}) => {
|
|
|
36
36
|
return `<div class="callout callout-${style}">\n<div class="callout-title">${title}</div>\n${innerHtml}</div>\n`
|
|
37
37
|
}
|
|
38
38
|
case 'bulletList': {
|
|
39
|
-
const itemsHtml = node.items ? node.items.map((item) => `<li>${renderInline(item)}</li>\n`).join('') : ''
|
|
39
|
+
const itemsHtml = node.items ? node.items.map((item) => `<li>${renderInline(item, options)}</li>\n`).join('') : ''
|
|
40
40
|
return `<ul>\n${itemsHtml}</ul>\n`
|
|
41
41
|
}
|
|
42
42
|
case 'orderedList': {
|
|
43
|
-
const itemsHtml = node.items ? node.items.map((item) => `<li>${renderInline(item)}</li>\n`).join('') : ''
|
|
43
|
+
const itemsHtml = node.items ? node.items.map((item) => `<li>${renderInline(item, options)}</li>\n`).join('') : ''
|
|
44
44
|
return `<ol>\n${itemsHtml}</ol>\n`
|
|
45
45
|
}
|
|
46
46
|
case 'table': {
|
|
@@ -57,7 +57,7 @@ const renderBlock = (node, options = {}) => {
|
|
|
57
57
|
.map((cell, colIdx) => {
|
|
58
58
|
const align = alignments[colIdx] || 'default'
|
|
59
59
|
const alignAttr = align !== 'default' ? ` align="${align}"` : ''
|
|
60
|
-
return `<${cellTag}${alignAttr}>${renderInline(cell)}</${cellTag}>`
|
|
60
|
+
return `<${cellTag}${alignAttr}>${renderInline(cell, options)}</${cellTag}>`
|
|
61
61
|
})
|
|
62
62
|
.join('')
|
|
63
63
|
return `<tr>${cells}</tr>\n`
|
|
@@ -73,6 +73,14 @@ const renderBlock = (node, options = {}) => {
|
|
|
73
73
|
case 'hr': {
|
|
74
74
|
return '<hr />\n'
|
|
75
75
|
}
|
|
76
|
+
case 'html': {
|
|
77
|
+
return `${node.value}\n`
|
|
78
|
+
}
|
|
79
|
+
case 'mathBlock': {
|
|
80
|
+
const mathRenderer = options.mathBlockRenderer || options.codeRenderers?.math
|
|
81
|
+
if (mathRenderer) return mathRenderer(node, options)
|
|
82
|
+
return `<div class="math-display" data-latex="${escapeHtml(node.value)}">$$\n${escapeHtml(node.value)}\n$$</div>\n`
|
|
83
|
+
}
|
|
76
84
|
default: {
|
|
77
85
|
return ''
|
|
78
86
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const { escapeHtml } = require('./escape')
|
|
2
2
|
|
|
3
|
-
const renderInline = (tokens) => {
|
|
3
|
+
const renderInline = (tokens, options = {}) => {
|
|
4
4
|
if (!tokens) return ''
|
|
5
5
|
if (!Array.isArray(tokens)) return escapeHtml(tokens)
|
|
6
6
|
|
|
@@ -13,15 +13,15 @@ const renderInline = (tokens) => {
|
|
|
13
13
|
case 'text':
|
|
14
14
|
return escapeHtml(token.value)
|
|
15
15
|
case 'bold':
|
|
16
|
-
return `<strong>${renderInline(token.children)}</strong>`
|
|
16
|
+
return `<strong>${renderInline(token.children, options)}</strong>`
|
|
17
17
|
case 'italic':
|
|
18
|
-
return `<em>${renderInline(token.children)}</em>`
|
|
18
|
+
return `<em>${renderInline(token.children, options)}</em>`
|
|
19
19
|
case 'strikethrough':
|
|
20
|
-
return `<del>${renderInline(token.children)}</del>`
|
|
20
|
+
return `<del>${renderInline(token.children, options)}</del>`
|
|
21
21
|
case 'code':
|
|
22
22
|
return `<code>${escapeHtml(token.value)}</code>`
|
|
23
23
|
case 'link':
|
|
24
|
-
return `<a href="${escapeHtml(token.url)}">${renderInline(token.children)}</a>`
|
|
24
|
+
return `<a href="${escapeHtml(token.url)}">${renderInline(token.children, options)}</a>`
|
|
25
25
|
case 'wikilink':
|
|
26
26
|
return `<a href="${escapeHtml(token.target)}">${escapeHtml(token.display)}</a>`
|
|
27
27
|
case 'image': {
|
|
@@ -36,6 +36,10 @@ const renderInline = (tokens) => {
|
|
|
36
36
|
}
|
|
37
37
|
case 'checkbox':
|
|
38
38
|
return `<input type="checkbox"${token.checked ? ' checked' : ''} disabled /> `
|
|
39
|
+
case 'inlineMath': {
|
|
40
|
+
if (options.inlineMathRenderer) return options.inlineMathRenderer(token, options)
|
|
41
|
+
return `<span class="math-inline" data-latex="${escapeHtml(token.value)}">$${escapeHtml(token.value)}$</span>`
|
|
42
|
+
}
|
|
39
43
|
case 'br':
|
|
40
44
|
return '<br />'
|
|
41
45
|
default:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sullux/markdown-html",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "A high-performance, bidirectional Markdown <-> HTML compiler supporting GFM, GitBook hints, syntax highlighting, and custom image dimensions.",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"author": "Charles Sullivan <charles@sullux.com>",
|