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 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
+ }