@f-o-t/markdown 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +305 -0
- package/dist/index.d.ts +1452 -0
- package/dist/index.js +3512 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# @f-o-t/markdown
|
|
2
|
+
|
|
3
|
+
A CommonMark compliant markdown parser with AST generation, streaming support, and bidirectional conversion (Markdown ↔ HTML).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# npm
|
|
9
|
+
npm install @f-o-t/markdown
|
|
10
|
+
|
|
11
|
+
# pnpm
|
|
12
|
+
pnpm add @f-o-t/markdown
|
|
13
|
+
|
|
14
|
+
# bun
|
|
15
|
+
bun add @f-o-t/markdown
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- **CommonMark compliant** - Full support for the CommonMark specification
|
|
21
|
+
- **GFM extensions** - Tables, task lists, strikethrough
|
|
22
|
+
- **Bidirectional conversion** - Parse markdown to AST, generate markdown from AST
|
|
23
|
+
- **HTML rendering** - Convert markdown AST to HTML with customization options
|
|
24
|
+
- **HTML to Markdown** - Convert HTML content back to markdown
|
|
25
|
+
- **Streaming support** - Process large files efficiently with streaming APIs
|
|
26
|
+
- **Batch processing** - Parse multiple files with progress events
|
|
27
|
+
- **Type-safe** - Full TypeScript support with Zod schemas for runtime validation
|
|
28
|
+
- **Zero dependencies** (except Zod for schema validation)
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
### Parsing Markdown
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { parse, parseOrThrow } from "@f-o-t/markdown";
|
|
36
|
+
|
|
37
|
+
// Safe parsing with result type
|
|
38
|
+
const result = parse("# Hello **World**");
|
|
39
|
+
if (result.success) {
|
|
40
|
+
console.log(result.data.root.children);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Throws on error
|
|
44
|
+
const doc = parseOrThrow("# Hello **World**");
|
|
45
|
+
console.log(doc.root.children);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Generating Markdown
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { generate, generateHeadingString, generateLinkString } from "@f-o-t/markdown";
|
|
52
|
+
|
|
53
|
+
// Generate from AST
|
|
54
|
+
const markdown = generate(doc);
|
|
55
|
+
|
|
56
|
+
// Helper functions for common elements
|
|
57
|
+
generateHeadingString(1, "Title"); // "# Title"
|
|
58
|
+
generateLinkString("Example", "https://example.com"); // "[Example](https://example.com)"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### HTML Rendering
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { parse, renderToHtml } from "@f-o-t/markdown";
|
|
65
|
+
|
|
66
|
+
const doc = parse("# Hello **World**");
|
|
67
|
+
const html = renderToHtml(doc.data);
|
|
68
|
+
// => "<h1 id="hello-world">Hello <strong>World</strong></h1>"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### HTML to Markdown
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { htmlToMarkdown } from "@f-o-t/markdown";
|
|
75
|
+
|
|
76
|
+
const markdown = htmlToMarkdown("<h1>Hello</h1><p>World</p>");
|
|
77
|
+
// => "# Hello\n\nWorld"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## API Reference
|
|
81
|
+
|
|
82
|
+
### Parsing
|
|
83
|
+
|
|
84
|
+
| Function | Description |
|
|
85
|
+
|----------|-------------|
|
|
86
|
+
| `parse(content, options?)` | Parses markdown, returns `{ success, data }` or `{ success, error }` |
|
|
87
|
+
| `parseOrThrow(content, options?)` | Parses markdown, throws on error |
|
|
88
|
+
| `parseToAst(content, options?)` | Returns just the AST root node |
|
|
89
|
+
| `parseBuffer(buffer, options?)` | Parses from `Uint8Array` with encoding detection |
|
|
90
|
+
| `parseBufferOrThrow(buffer, options?)` | Same as above, throws on error |
|
|
91
|
+
| `isValidMarkdown(content)` | Returns `true` if content can be parsed |
|
|
92
|
+
|
|
93
|
+
#### Parse Options
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
interface ParseOptions {
|
|
97
|
+
positions?: boolean; // Include position info (default: true)
|
|
98
|
+
preserveSource?: boolean; // Keep original source (default: false)
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Utility Functions
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import {
|
|
106
|
+
extractText,
|
|
107
|
+
countWords,
|
|
108
|
+
getHeadings,
|
|
109
|
+
getLinks,
|
|
110
|
+
getImages,
|
|
111
|
+
getCodeBlocks,
|
|
112
|
+
} from "@f-o-t/markdown";
|
|
113
|
+
|
|
114
|
+
extractText("**Hello** *world*"); // "Hello world"
|
|
115
|
+
countWords("Hello **world**!"); // 2
|
|
116
|
+
getHeadings("# Title\n## Section"); // [{ level: 1, text: "Title" }, ...]
|
|
117
|
+
getLinks("[Example](url)"); // [{ text: "Example", url: "url" }]
|
|
118
|
+
getImages(""); // [{ alt: "Alt", url: "image.png" }]
|
|
119
|
+
getCodeBlocks("```js\ncode\n```"); // [{ lang: "js", code: "code" }]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Generation
|
|
123
|
+
|
|
124
|
+
| Function | Description |
|
|
125
|
+
|----------|-------------|
|
|
126
|
+
| `generate(document, options?)` | Generates markdown from AST |
|
|
127
|
+
| `generateNode(node, options?)` | Generates markdown from any node |
|
|
128
|
+
| `createGenerator(options?)` | Creates incremental generator |
|
|
129
|
+
|
|
130
|
+
#### Generate Options
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
interface GenerateOptions {
|
|
134
|
+
lineEnding?: "\n" | "\r\n"; // Default: "\n"
|
|
135
|
+
indent?: number; // List indent (default: 3)
|
|
136
|
+
setext?: boolean; // Setext headings (default: false)
|
|
137
|
+
fence?: "`" | "~"; // Code fence char (default: "`")
|
|
138
|
+
fenceLength?: number; // Fence length (default: 3)
|
|
139
|
+
emphasis?: "*" | "_"; // Emphasis marker (default: "*")
|
|
140
|
+
strong?: "**" | "__"; // Strong marker (default: "**")
|
|
141
|
+
bullet?: "-" | "*" | "+"; // List bullet (default: "-")
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
#### String Helpers
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import {
|
|
149
|
+
generateHeadingString,
|
|
150
|
+
generateLinkString,
|
|
151
|
+
generateImageString,
|
|
152
|
+
generateCodeBlockString,
|
|
153
|
+
generateListString,
|
|
154
|
+
generateBlockquoteString,
|
|
155
|
+
generateEmphasisString,
|
|
156
|
+
generateStrongString,
|
|
157
|
+
generateInlineCodeString,
|
|
158
|
+
generateTableString,
|
|
159
|
+
generateTaskListString,
|
|
160
|
+
generateStrikethroughString,
|
|
161
|
+
} from "@f-o-t/markdown";
|
|
162
|
+
|
|
163
|
+
// Examples
|
|
164
|
+
generateHeadingString(2, "Section"); // "## Section"
|
|
165
|
+
generateCodeBlockString("const x = 1;", "js"); // "```js\nconst x = 1;\n```"
|
|
166
|
+
generateListString(["A", "B"], true); // "1. A\n2. B"
|
|
167
|
+
generateTableString(["Name", "Age"], [["Alice", "30"]]);
|
|
168
|
+
generateTaskListString([{ text: "Done", checked: true }]);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### HTML Rendering
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { renderToHtml, renderNodeToHtml } from "@f-o-t/markdown";
|
|
175
|
+
|
|
176
|
+
const html = renderToHtml(document, {
|
|
177
|
+
sanitizeHtml: true, // Escape raw HTML (default: true)
|
|
178
|
+
externalLinksNewTab: false, // Add target="_blank" (default: false)
|
|
179
|
+
classPrefix: "", // CSS class prefix
|
|
180
|
+
softBreakAsBr: false, // Render soft breaks as <br>
|
|
181
|
+
transformUrl: (url, type) => url, // Custom URL transformer
|
|
182
|
+
elementAttributes: { // Custom attributes per element
|
|
183
|
+
link: { rel: "nofollow" },
|
|
184
|
+
image: { loading: "lazy" },
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### HTML to Markdown
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { htmlToMarkdown, parseHtml, htmlAstToMarkdownAst } from "@f-o-t/markdown";
|
|
193
|
+
|
|
194
|
+
// Direct conversion
|
|
195
|
+
const markdown = htmlToMarkdown("<h1>Hello</h1>");
|
|
196
|
+
|
|
197
|
+
// Or step by step
|
|
198
|
+
const htmlAst = parseHtml("<h1>Hello</h1>");
|
|
199
|
+
const markdownAst = htmlAstToMarkdownAst(htmlAst);
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Streaming
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
import {
|
|
206
|
+
parseStream,
|
|
207
|
+
parseStreamToDocument,
|
|
208
|
+
parseBatchStream,
|
|
209
|
+
} from "@f-o-t/markdown";
|
|
210
|
+
|
|
211
|
+
// Stream from fetch
|
|
212
|
+
const response = await fetch("large-doc.md");
|
|
213
|
+
for await (const event of parseStream(response.body)) {
|
|
214
|
+
if (event.type === "block") {
|
|
215
|
+
console.log("Parsed block:", event.data.type);
|
|
216
|
+
} else if (event.type === "complete") {
|
|
217
|
+
console.log("Done!");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Collect to document
|
|
222
|
+
const doc = await parseStreamToDocument(response.body);
|
|
223
|
+
|
|
224
|
+
// Batch processing
|
|
225
|
+
const files = [
|
|
226
|
+
{ filename: "a.md", content: "# A" },
|
|
227
|
+
{ filename: "b.md", content: "# B" },
|
|
228
|
+
];
|
|
229
|
+
for await (const event of parseBatchStream(files)) {
|
|
230
|
+
switch (event.type) {
|
|
231
|
+
case "file_start":
|
|
232
|
+
console.log(`Processing ${event.filename}`);
|
|
233
|
+
break;
|
|
234
|
+
case "file_complete":
|
|
235
|
+
console.log(`Completed ${event.filename}`);
|
|
236
|
+
break;
|
|
237
|
+
case "batch_complete":
|
|
238
|
+
console.log(`Done: ${event.totalFiles} files`);
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
#### Stream Options
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
interface StreamOptions {
|
|
248
|
+
positions?: boolean; // Include positions (default: true)
|
|
249
|
+
maxBufferSize?: number; // Max buffer size in bytes (default: 10MB)
|
|
250
|
+
chunkSize?: number; // Chunk size for batch (default: 64KB)
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### AST Types
|
|
255
|
+
|
|
256
|
+
All node types are available as TypeScript types and Zod schemas:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import type {
|
|
260
|
+
MarkdownDocument,
|
|
261
|
+
DocumentNode,
|
|
262
|
+
BlockNode,
|
|
263
|
+
InlineNode,
|
|
264
|
+
HeadingNode,
|
|
265
|
+
ParagraphNode,
|
|
266
|
+
CodeBlockNode,
|
|
267
|
+
ListNode,
|
|
268
|
+
TableNode,
|
|
269
|
+
// ... and more
|
|
270
|
+
} from "@f-o-t/markdown";
|
|
271
|
+
|
|
272
|
+
import {
|
|
273
|
+
documentNodeSchema,
|
|
274
|
+
headingNodeSchema,
|
|
275
|
+
paragraphNodeSchema,
|
|
276
|
+
// ... Zod schemas for validation
|
|
277
|
+
} from "@f-o-t/markdown";
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Utilities
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
import {
|
|
284
|
+
normalizeLineEndings,
|
|
285
|
+
normalizeEscapedNewlines,
|
|
286
|
+
normalizeMarkdownEmphasis,
|
|
287
|
+
} from "@f-o-t/markdown";
|
|
288
|
+
|
|
289
|
+
// Normalize to \n
|
|
290
|
+
normalizeLineEndings("hello\r\nworld"); // "hello\nworld"
|
|
291
|
+
|
|
292
|
+
// Convert escaped newlines to actual newlines
|
|
293
|
+
normalizeEscapedNewlines("hello\\nworld"); // "hello\nworld"
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## CommonMark Compliance
|
|
297
|
+
|
|
298
|
+
This library implements the [CommonMark specification](https://commonmark.org/) with additional support for GFM (GitHub Flavored Markdown) extensions:
|
|
299
|
+
|
|
300
|
+
- **Block elements**: Headings, paragraphs, code blocks (fenced and indented), blockquotes, lists (ordered, unordered, task lists), thematic breaks, HTML blocks, tables
|
|
301
|
+
- **Inline elements**: Emphasis, strong emphasis, links, images, code spans, hard/soft breaks, HTML inline, strikethrough
|
|
302
|
+
|
|
303
|
+
## License
|
|
304
|
+
|
|
305
|
+
MIT
|