auwla-markdown 0.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 +301 -0
- package/bun.lock +131 -0
- package/package.json +29 -0
- package/src/adapters/shiki.ts +42 -0
- package/src/engine.ts +68 -0
- package/src/features.ts +412 -0
- package/src/frontmatter.ts +31 -0
- package/src/headings.ts +45 -0
- package/src/index.ts +3 -0
- package/src/types.ts +32 -0
- package/tests/engine.test.ts +173 -0
- package/tests/fixtures/sample.md +159 -0
- package/tests/live-test.ts +239 -0
- package/tests/output/sample.html +233 -0
- package/tests/output/sample.json +55 -0
- package/tsconfig.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# `@auwla/markdown`
|
|
2
|
+
|
|
3
|
+
A zero-dependency, type-safe Markdown compilation engine with custom component tags, code-block filename headers, line-highlighting, and interactive tabs. Designed for premium developer documentation and Static Site Generation (SSG).
|
|
4
|
+
|
|
5
|
+
> [!NOTE]
|
|
6
|
+
> **Full Standard Markdown Support:** `@auwla/markdown` is completely backward-compatible. It supports all default, standard Markdown documents (fences, headings, lists, bold text) out of the box. Our custom tag-based component syntax (`=<TagName>`) is designed as a completely **optional layer** to improve markup readability and provide advanced UI customizations (like tabs, collapsible callouts, and customized tables).
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 🚀 Key Features
|
|
11
|
+
|
|
12
|
+
* **Visual Component Tags (`=<TagName>`):** Dynamic components registry with built-in or custom elements.
|
|
13
|
+
* **Header Metadata:** `=<Header>` block strips frontmatter configuration from HTML body automatically.
|
|
14
|
+
* **Collapsible Callouts:** Native `<details>`/`<summary>` card blocks.
|
|
15
|
+
* **Tag-Based Tab Panels:** Multi-tab toggle sections using `=<Tabs>` and `=<Tab>`.
|
|
16
|
+
* **Zero Client-Side JS Overhead:** Interactive UI elements use tiny, inline, vanilla handlers.
|
|
17
|
+
* **Code Blocks Meta:** Support filename badges `[Counter.tsx]` and line range highlights `{7-9}`.
|
|
18
|
+
* **Header Anchors:** Self-linking `#` anchors generated for both markdown and tag-based headings.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 📦 Getting Started
|
|
23
|
+
|
|
24
|
+
### 1. Install the Package
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bun add @auwla/markdown
|
|
28
|
+
# or npm install @auwla/markdown
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 2. Initialize the Compiler Configuration
|
|
32
|
+
|
|
33
|
+
Create a markdown parser configuration file:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// src/markdown.config.ts
|
|
37
|
+
import { createMDConfig, shikiHighlighter } from '@auwla/markdown';
|
|
38
|
+
|
|
39
|
+
export const mdParser = createMDConfig({
|
|
40
|
+
// Pluggable highlighter adapter (runs at compile-time)
|
|
41
|
+
highlighter: shikiHighlighter({
|
|
42
|
+
theme: 'github-dark',
|
|
43
|
+
langs: ['typescript', 'tsx', 'javascript', 'jsx', 'bash', 'html', 'css']
|
|
44
|
+
}),
|
|
45
|
+
|
|
46
|
+
// Toggle built-in features
|
|
47
|
+
features: {
|
|
48
|
+
copyCodeButton: true, // Adds interactive "Copy" button to code blocks
|
|
49
|
+
headerAnchors: true // Appends self-linking hover anchors (#) to headings
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// Register custom component tags
|
|
53
|
+
components: {
|
|
54
|
+
MyCard: (props, rawContent, parse) => `
|
|
55
|
+
<div class="card ${props.theme || 'light'}">
|
|
56
|
+
<h3>${props.title}</h3>
|
|
57
|
+
<div class="body">${parse(rawContent)}</div>
|
|
58
|
+
</div>
|
|
59
|
+
`
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 📑 `=<Header>` Metadata Syntax
|
|
67
|
+
|
|
68
|
+
Traditional frontmatter YAML markers (`---`) are replaced by the `=<Header>` block placed at the **very top of the file**:
|
|
69
|
+
|
|
70
|
+
```markdown
|
|
71
|
+
=<Header>
|
|
72
|
+
title: Premium Documentation Guide
|
|
73
|
+
author: Amihere Theophilus Junior
|
|
74
|
+
version: 1.0.0
|
|
75
|
+
draft: false
|
|
76
|
+
=</Header>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Metadata Features:
|
|
80
|
+
* **Parsing & Extraction:** The parsed fields are returned programmatically under the `meta` key.
|
|
81
|
+
* **HTML Stripping:** The entire `=<Header>` block is stripped completely from the returned HTML body so it never renders inside page content.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 🧩 Built-in Component Tags
|
|
86
|
+
|
|
87
|
+
All custom tags start with `=<` and close with `=</TagName>` or are self-closing `/>`.
|
|
88
|
+
|
|
89
|
+
### Customization & Attribute Forwarding:
|
|
90
|
+
Every built-in tag supports **easy CSS class targeting** and **raw HTML attribute forwarding**:
|
|
91
|
+
* **Class name merging:** If you pass `class="..."` on any tag, the custom class names are automatically appended to the component's default class name (e.g. `<div class="callout callout-note my-custom-class">`).
|
|
92
|
+
* **HTML attribute forwarding:** Any custom HTML properties (like `style="..."`, `colspan="..."`, `rowspan="..."`) passed to a component are directly forwarded to the compiled HTML tag (e.g. `<table class="auwla-table" style="color: red;">`).
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### 1. `=<Callout>`
|
|
97
|
+
Displays highlighted message blocks.
|
|
98
|
+
|
|
99
|
+
* **Attributes:**
|
|
100
|
+
* `type`: Modifies color theme border. Supported: `note`, `tip`, `important`, `warning`, `caution`. (Default: `note`).
|
|
101
|
+
* `title`: Overrides default uppercase header text.
|
|
102
|
+
* `collapsible`: Boolean or presence marker (`collapsible`, `collapsible="true"`). If present, compiles to native interactive `<details>` and `<summary>` elements.
|
|
103
|
+
* `collapsed`: Boolean (`"true"` / `"false"`). If collapsible, sets whether it is closed by default.
|
|
104
|
+
* `class`: Custom classes merged onto the element wrapper.
|
|
105
|
+
* *Additional attributes:* Any other attribute (e.g. `style="..."`) is forwarded directly.
|
|
106
|
+
|
|
107
|
+
```markdown
|
|
108
|
+
/* Simple Standard Callout */
|
|
109
|
+
=<Callout type="note" title="Useful Note">
|
|
110
|
+
This is a standard callout.
|
|
111
|
+
=</Callout>
|
|
112
|
+
|
|
113
|
+
/* Interactive Collapsible Callout */
|
|
114
|
+
=<Callout type="tip" title="Advanced Tip" collapsible>
|
|
115
|
+
Here is some detailed compilation info that is collapsed by default.
|
|
116
|
+
=</Callout>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
### 2. `=<Tabs>` and `=<Tab>`
|
|
122
|
+
Renders selection tabs with zero-dependency toggle buttons.
|
|
123
|
+
|
|
124
|
+
* **`=<Tabs>`** (Parent Container): Parses nested panels and binds click state handlers. Supports `class` and `style` forwarding.
|
|
125
|
+
* **`=<Tab>`** (Panel):
|
|
126
|
+
* `title`: The label displayed on the tab button (required).
|
|
127
|
+
* Supports `class` and `style` forwarding (e.g., custom tab panels).
|
|
128
|
+
|
|
129
|
+
```markdown
|
|
130
|
+
=<Tabs class="my-custom-tabs">
|
|
131
|
+
=<Tab title="TSX Component" class="first-panel">
|
|
132
|
+
```tsx [Counter.tsx]
|
|
133
|
+
import { reactive } from 'auwla';
|
|
134
|
+
export default function Counter() { ... }
|
|
135
|
+
```
|
|
136
|
+
=</Tab>
|
|
137
|
+
|
|
138
|
+
=<Tab title="Bash CLI">
|
|
139
|
+
```bash
|
|
140
|
+
bun install @auwla/markdown
|
|
141
|
+
```
|
|
142
|
+
=</Tab>
|
|
143
|
+
=</Tabs>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
### 3. Custom Tables (`=<Table>`, `=<Row>`, `=<Column>`, `=<Cell>`)
|
|
149
|
+
Structural tag-based table elements.
|
|
150
|
+
|
|
151
|
+
* **`=<Table>`** (Wrapper): Accepts table attributes (like `border="1"`, `class`, or `style`).
|
|
152
|
+
* **`=<Row>`** (Row): Compiles to `<tr>`. Supports class/style forwarding.
|
|
153
|
+
* **`=<Column>`** (Header Cell): Compiles to `<th>`. Supports `align` (`"left"`, `"center"`, `"right"`), `colspan`, `rowspan`, class, and style.
|
|
154
|
+
* **`=<Cell>`** (Data Cell): Compiles to `<td>`. Supports `align` (`"left"`, `"center"`, `"right"`), `colspan`, `rowspan`, class, and style.
|
|
155
|
+
|
|
156
|
+
```markdown
|
|
157
|
+
=<Table style="color: red;">
|
|
158
|
+
=<Row class="header-row">
|
|
159
|
+
=<Column align="left" colspan="2">Framework Details=</Column>
|
|
160
|
+
</Row>
|
|
161
|
+
=<Row>
|
|
162
|
+
=<Cell align="left">**Auwla**=</Cell>
|
|
163
|
+
=<Cell align="center">~7 kB=</Cell>
|
|
164
|
+
</Row>
|
|
165
|
+
</Table>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
### 4. Custom Headings (`=<h1>` through `=<h6>`)
|
|
171
|
+
Component-driven headings:
|
|
172
|
+
* **Auto-generated IDs:** Heading text is slugified to generate `id="..."` attributes automatically.
|
|
173
|
+
* **Custom IDs:** Custom identifiers are preserved if defined explicitly on the tag (e.g. `=<h2 id="custom-anchor">`).
|
|
174
|
+
* **Hover Anchors:** Self-linking `#` anchors are injected inside heading elements when `features.headerAnchors` is enabled.
|
|
175
|
+
|
|
176
|
+
```markdown
|
|
177
|
+
=<h1>Getting Started=</h1>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
### 5. Fallback Wrappers (Lowercase Tags)
|
|
183
|
+
Standard lowercase HTML tag markers (e.g., `=<p>`, `=<span>`, `=<a>`) can be written directly:
|
|
184
|
+
* **No Double Nesting:** Children inside standard inline wrapper tags (like `p`, `span`, `h1`-`h6`, `li`, `a`) are compiled in **inline mode** to prevent double-block wrapping (e.g., `<p><p>...</p></p>`).
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 📝 Code Block Metadata
|
|
189
|
+
|
|
190
|
+
Annotations are parsed directly from the language fence:
|
|
191
|
+
* **Filename badge:** Add square brackets `[filename.ext]` to render a filename tab header above the code block.
|
|
192
|
+
* **Line Highlights:** Add curly braces `{range}` (e.g. `{1-3,5}`) to highlight specific lines.
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
```tsx [Counter.tsx] {3,5-7}
|
|
196
|
+
import { reactive } from 'auwla';
|
|
197
|
+
// This line is highlighted
|
|
198
|
+
const a = 1;
|
|
199
|
+
// These lines are highlighted
|
|
200
|
+
const b = 2;
|
|
201
|
+
const c = 3;
|
|
202
|
+
```
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 💅 Styling Class Schema
|
|
208
|
+
|
|
209
|
+
Styling class names generated in the HTML output:
|
|
210
|
+
|
|
211
|
+
| Component | Class Name | Description |
|
|
212
|
+
| :--- | :--- | :--- |
|
|
213
|
+
| **Callout** | `.auwla-callout` | Main card element. |
|
|
214
|
+
| **Callout Type** | `.auwla-callout-note` | Modifier class based on `type="..."`. |
|
|
215
|
+
| **Callout Title** | `.auwla-callout-title` | Title bar elements. |
|
|
216
|
+
| **Callout Content** | `.auwla-callout-content` | Wrapped markdown content body. |
|
|
217
|
+
| **Tabs Wrapper** | `.auwla-tabs-container` | Main tab selector wrapper. |
|
|
218
|
+
| **Tabs Header** | `.auwla-tabs-header` | Row containing tab buttons. |
|
|
219
|
+
| **Tab Button** | `.auwla-tab-btn` | Interactive tab toggle button. |
|
|
220
|
+
| **Active Tab** | `.auwla-tab-btn.active` | Target tab state. |
|
|
221
|
+
| **Tab Panel** | `.auwla-tab-panel` | Display body container. |
|
|
222
|
+
| **Table** | `.auwla-table` | Global table selector class name. |
|
|
223
|
+
| **Header Anchor** | `.header-anchor` | Hoverable link symbol pointing to heading IDs. |
|
|
224
|
+
| **Copy Button** | `.copy-code-btn` | Interactive absolute-positioned copy button. |
|
|
225
|
+
| **Code Wrapper** | `.code-block-wrapper` | Relative div wrapper surrounding pre/code blocks. |
|
|
226
|
+
| **Code Filename** | `.code-block-filename` | Header text element showing the filename. |
|
|
227
|
+
| **Line Highlight** | `.highlighted-line` | Highlighted code line helper class. |
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## 🛠️ Framework Integration Examples
|
|
232
|
+
|
|
233
|
+
`@auwla/markdown` compiles down to plain HTML and metadata objects, making it compatible with any modern JS framework.
|
|
234
|
+
|
|
235
|
+
### 1. Vanilla JS / Node / Bun
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
import { mdParser } from './markdown.config';
|
|
239
|
+
|
|
240
|
+
const rawMarkdown = `
|
|
241
|
+
=<Header>
|
|
242
|
+
title: Standalone Markdown Engine
|
|
243
|
+
=</Header>
|
|
244
|
+
# Welcome
|
|
245
|
+
`;
|
|
246
|
+
|
|
247
|
+
const { html, meta, headings } = await mdParser.parse(rawMarkdown);
|
|
248
|
+
console.log(meta.title); // "Standalone Markdown Engine"
|
|
249
|
+
console.log(html); // "<h1>Welcome</h1>"
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### 2. Svelte / React / Solid (Dynamic Render)
|
|
253
|
+
|
|
254
|
+
Pass the generated HTML string directly to your framework's raw HTML injector:
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
// React / Solid
|
|
258
|
+
export function DocView({ html }) {
|
|
259
|
+
return <article dangerouslySetInnerHTML={{ __html: html }} />;
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
```svelte
|
|
264
|
+
<!-- Svelte -->
|
|
265
|
+
<article>
|
|
266
|
+
{@html html}
|
|
267
|
+
</article>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### 3. Static Site Generation (SSG) in Auwla
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
// src/pages/docs/[slug].tsx
|
|
274
|
+
import { getRouted, type RouteContext } from 'auwla/router';
|
|
275
|
+
import { mdParser } from '../../markdown.config';
|
|
276
|
+
|
|
277
|
+
export const config = {
|
|
278
|
+
renderMode: 'ssg',
|
|
279
|
+
async generatePaths() {
|
|
280
|
+
return [{ slug: 'introduction' }, { slug: 'installation' }];
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
export async function routed(ctx: RouteContext<'/docs/:slug'>) {
|
|
285
|
+
const rawMarkdown = await loadMarkdownFile(ctx.params.slug);
|
|
286
|
+
const { html, meta, headings } = await mdParser.parse(rawMarkdown);
|
|
287
|
+
return { html, meta, headings };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export default function DocPage() {
|
|
291
|
+
const data = getRouted(routed)?.value;
|
|
292
|
+
if (!data) return () => <div>Loading...</div>;
|
|
293
|
+
|
|
294
|
+
return () => (
|
|
295
|
+
<div class="docs-layout">
|
|
296
|
+
<h1>{data.meta.title}</h1>
|
|
297
|
+
<article dangerouslySetInnerHTML={{ __html: data.html }} />
|
|
298
|
+
</div>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
```
|
package/bun.lock
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "markdown",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"marked": "^18.0.5",
|
|
9
|
+
"marked-highlight": "^2.2.4",
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@types/bun": "latest",
|
|
13
|
+
"@types/prismjs": "^1.26.6",
|
|
14
|
+
"prismjs": "^1.30.0",
|
|
15
|
+
"shiki": "^4.3.0",
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"typescript": "^5",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
"packages": {
|
|
23
|
+
"@shikijs/core": ["@shikijs/core@4.3.0", "", { "dependencies": { "@shikijs/primitive": "4.3.0", "@shikijs/types": "4.3.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-EooU3i9F6IAE8kEu+AnGf9DFZWkQBZ+hJn3tLVbsH+61mtQiva5biai66fAA6nvFPXkLgvrh7BrR7YcJU83xQQ=="],
|
|
24
|
+
|
|
25
|
+
"@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.3.0", "", { "dependencies": { "@shikijs/types": "4.3.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-hTv/KiFf2tpiqlACPiztGGurEARWIutB8YUhcrA1pUC7VzzwKO+g5crUocrLztrZ5ro5Z4hbXg7bYclETn3gSQ=="],
|
|
26
|
+
|
|
27
|
+
"@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.3.0", "", { "dependencies": { "@shikijs/types": "4.3.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-1vMdN3gHfnKfLYwecUI2ITJI4RhHt96xEaJumVn7Heb0IlJ8WQMIH0Voak+2j22BpSNKdnOfB/pCTPnPm2gq7A=="],
|
|
28
|
+
|
|
29
|
+
"@shikijs/langs": ["@shikijs/langs@4.3.0", "", { "dependencies": { "@shikijs/types": "4.3.0" } }, "sha512-rnlqFbBRSys9bT4gl/5rw9RnS0W/I84ZldXPkO7cvlEMoV85TyF/aU01N7/NbSR776RNLjrJKjfFUXJR6wN1Cg=="],
|
|
30
|
+
|
|
31
|
+
"@shikijs/primitive": ["@shikijs/primitive@4.3.0", "", { "dependencies": { "@shikijs/types": "4.3.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-CPkz64PTa5diRW1ggzMZH9VM/du4RNChYgVtgqrFcgruvIybmCvySv8GkiHSczUHXYuuR8TdKEwFx+UnZMpgdg=="],
|
|
32
|
+
|
|
33
|
+
"@shikijs/themes": ["@shikijs/themes@4.3.0", "", { "dependencies": { "@shikijs/types": "4.3.0" } }, "sha512-Avgt05YiT+Y3prjIc9lmQxhJzHBcCfR6cjiFW4OyaMBbt2A6trX5rfjUzx+Vj/mE9qpArYjatnqo9XPjQNW/AQ=="],
|
|
34
|
+
|
|
35
|
+
"@shikijs/types": ["@shikijs/types@4.3.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oc8b9U2SYvofKZk8e/737nIX0qwf6eV2vHFATeObAu7r+mUVpLs8Re0BmVkIjAWAYgkmG/CzLNo7rzuBzRu/wQ=="],
|
|
36
|
+
|
|
37
|
+
"@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
|
|
38
|
+
|
|
39
|
+
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
|
|
40
|
+
|
|
41
|
+
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
|
|
42
|
+
|
|
43
|
+
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
|
|
44
|
+
|
|
45
|
+
"@types/node": ["@types/node@26.0.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw=="],
|
|
46
|
+
|
|
47
|
+
"@types/prismjs": ["@types/prismjs@1.26.6", "", {}, "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw=="],
|
|
48
|
+
|
|
49
|
+
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
|
|
50
|
+
|
|
51
|
+
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.2", "", {}, "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA=="],
|
|
52
|
+
|
|
53
|
+
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
|
54
|
+
|
|
55
|
+
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
|
|
56
|
+
|
|
57
|
+
"character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
|
|
58
|
+
|
|
59
|
+
"character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
|
|
60
|
+
|
|
61
|
+
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
|
|
62
|
+
|
|
63
|
+
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
|
64
|
+
|
|
65
|
+
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
|
|
66
|
+
|
|
67
|
+
"hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
|
|
68
|
+
|
|
69
|
+
"hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],
|
|
70
|
+
|
|
71
|
+
"html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="],
|
|
72
|
+
|
|
73
|
+
"marked": ["marked@18.0.5", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w=="],
|
|
74
|
+
|
|
75
|
+
"marked-highlight": ["marked-highlight@2.2.4", "", { "peerDependencies": { "marked": ">=4 <19" } }, "sha512-PZxisNMJDduSjc0q6uvjsnqqHCXc9s0eyzxDO9sB1eNGJnd/H1/Fu+z6g/liC1dfJdFW4SftMwMlLvsBhUPrqQ=="],
|
|
76
|
+
|
|
77
|
+
"mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="],
|
|
78
|
+
|
|
79
|
+
"micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
|
|
80
|
+
|
|
81
|
+
"micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
|
|
82
|
+
|
|
83
|
+
"micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="],
|
|
84
|
+
|
|
85
|
+
"micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="],
|
|
86
|
+
|
|
87
|
+
"micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="],
|
|
88
|
+
|
|
89
|
+
"oniguruma-parser": ["oniguruma-parser@0.12.2", "", {}, "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw=="],
|
|
90
|
+
|
|
91
|
+
"oniguruma-to-es": ["oniguruma-to-es@4.3.6", "", { "dependencies": { "oniguruma-parser": "^0.12.2", "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA=="],
|
|
92
|
+
|
|
93
|
+
"prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
|
|
94
|
+
|
|
95
|
+
"property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="],
|
|
96
|
+
|
|
97
|
+
"regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="],
|
|
98
|
+
|
|
99
|
+
"regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="],
|
|
100
|
+
|
|
101
|
+
"regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="],
|
|
102
|
+
|
|
103
|
+
"shiki": ["shiki@4.3.0", "", { "dependencies": { "@shikijs/core": "4.3.0", "@shikijs/engine-javascript": "4.3.0", "@shikijs/engine-oniguruma": "4.3.0", "@shikijs/langs": "4.3.0", "@shikijs/themes": "4.3.0", "@shikijs/types": "4.3.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-NKKjWzR6LIGL3sXBrWDw9sDS9cxx42/DkysaNqJEeOWE8Kix5gpak0bc00OfDVEO4oyXSyz8+aRaqKoBD1yo7A=="],
|
|
104
|
+
|
|
105
|
+
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
|
|
106
|
+
|
|
107
|
+
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
|
|
108
|
+
|
|
109
|
+
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
|
|
110
|
+
|
|
111
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
112
|
+
|
|
113
|
+
"undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
|
|
114
|
+
|
|
115
|
+
"unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="],
|
|
116
|
+
|
|
117
|
+
"unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="],
|
|
118
|
+
|
|
119
|
+
"unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
|
|
120
|
+
|
|
121
|
+
"unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="],
|
|
122
|
+
|
|
123
|
+
"unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="],
|
|
124
|
+
|
|
125
|
+
"vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
|
|
126
|
+
|
|
127
|
+
"vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
|
|
128
|
+
|
|
129
|
+
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
|
130
|
+
}
|
|
131
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "auwla-markdown",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"main": "src/index.ts",
|
|
5
|
+
"module": "src/index.ts",
|
|
6
|
+
"types": "src/index.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"devDependencies": {
|
|
9
|
+
"@types/bun": "latest",
|
|
10
|
+
"@types/prismjs": "^1.26.6",
|
|
11
|
+
"prismjs": "^1.30.0",
|
|
12
|
+
"shiki": "^4.3.0"
|
|
13
|
+
},
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"typescript": "^5"
|
|
16
|
+
},
|
|
17
|
+
"peerDependenciesMeta": {
|
|
18
|
+
"shiki": {
|
|
19
|
+
"optional": "true"
|
|
20
|
+
},
|
|
21
|
+
"prismjs": {
|
|
22
|
+
"optional": "true"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"marked": "^18.0.5",
|
|
27
|
+
"marked-highlight": "^2.2.4"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { HighlighterAdapter } from '../types';
|
|
2
|
+
import type { BundledLanguage, BundledTheme, Highlighter } from 'shiki';
|
|
3
|
+
|
|
4
|
+
export interface ShikiAdapterOptions {
|
|
5
|
+
theme?: BundledTheme | { light: BundledTheme; dark: BundledTheme };
|
|
6
|
+
langs?: BundledLanguage[]; // Pre-load specific languages for speed
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function shikiHighlighter(options: ShikiAdapterOptions = {}): HighlighterAdapter {
|
|
10
|
+
let shikiInstance: Highlighter | null = null;
|
|
11
|
+
const theme = options.theme || 'github-dark';
|
|
12
|
+
|
|
13
|
+
return async (code: string, language: string) => {
|
|
14
|
+
// 1. Dynamically import Shiki ONLY when this function is actually called
|
|
15
|
+
const { createHighlighter } = await import('shiki');
|
|
16
|
+
|
|
17
|
+
// 2. Initialize it as a singleton on the first run
|
|
18
|
+
if (!shikiInstance) {
|
|
19
|
+
shikiInstance = await createHighlighter({
|
|
20
|
+
themes: typeof theme === 'string' ? [theme] : [theme.light, theme.dark],
|
|
21
|
+
langs: options.langs || [language as BundledLanguage, 'javascript', 'typescript', 'bash', 'html', 'css'],
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 3. Load the language if it hasn't been loaded yet (catch-all for unexpected languages)
|
|
26
|
+
if (!shikiInstance.getLoadedLanguages().includes(language)) {
|
|
27
|
+
await shikiInstance.loadLanguage(language as BundledLanguage).catch(() => {});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const shikiOptions: any = {
|
|
31
|
+
lang: language,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
if (typeof theme === 'string') {
|
|
35
|
+
shikiOptions.theme = theme;
|
|
36
|
+
} else {
|
|
37
|
+
shikiOptions.themes = theme;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return shikiInstance.codeToHtml(code, shikiOptions);
|
|
41
|
+
};
|
|
42
|
+
}
|
package/src/engine.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Marked } from 'marked';
|
|
2
|
+
import { markedHighlight } from 'marked-highlight';
|
|
3
|
+
import type { MarkdownConfig, MarkdownEngine, ParsedMarkdown } from './types';
|
|
4
|
+
import { extractFrontmatter } from './frontmatter';
|
|
5
|
+
import { extractHeadings } from './headings';
|
|
6
|
+
import {
|
|
7
|
+
preprocessComponents,
|
|
8
|
+
copyCodeButtonRenderer,
|
|
9
|
+
headerAnchorsRenderer,
|
|
10
|
+
} from './features';
|
|
11
|
+
|
|
12
|
+
/** Configures and returns an isolated instance of the Markdown parsing engine. */
|
|
13
|
+
export function createMDConfig(config: MarkdownConfig = {}): MarkdownEngine {
|
|
14
|
+
const markedInstance = new Marked({
|
|
15
|
+
gfm: true,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
if (config.highlighter) {
|
|
19
|
+
markedInstance.use(
|
|
20
|
+
markedHighlight({
|
|
21
|
+
async: true,
|
|
22
|
+
highlight: async (code, lang) => {
|
|
23
|
+
if (!lang) return code;
|
|
24
|
+
return await (config.highlighter as any)(code, lang);
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Register built-in features using custom Marked renderers
|
|
31
|
+
const customRenderer: any = {};
|
|
32
|
+
|
|
33
|
+
if (config.features?.headerAnchors) {
|
|
34
|
+
customRenderer.heading = headerAnchorsRenderer;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (config.features?.copyCodeButton) {
|
|
38
|
+
customRenderer.code = copyCodeButtonRenderer;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (Object.keys(customRenderer).length > 0) {
|
|
42
|
+
markedInstance.use({ renderer: customRenderer });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
parse: async (rawString: string): Promise<ParsedMarkdown> => {
|
|
47
|
+
let { content, meta } = extractFrontmatter(rawString);
|
|
48
|
+
const headings = extractHeadings(content);
|
|
49
|
+
|
|
50
|
+
// Preprocess component blocks and custom tags before sending to Marked
|
|
51
|
+
content = await preprocessComponents(
|
|
52
|
+
content,
|
|
53
|
+
async (str) => await markedInstance.parse(str),
|
|
54
|
+
async (str) => await markedInstance.parseInline(str),
|
|
55
|
+
config.features,
|
|
56
|
+
config.components
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const html = await markedInstance.parse(content);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
html,
|
|
63
|
+
meta,
|
|
64
|
+
headings,
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|