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
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { expect, test, describe } from "bun:test";
|
|
2
|
+
import { createMDConfig } from "../src/index";
|
|
3
|
+
|
|
4
|
+
describe("Markdown Engine - Component Tags", () => {
|
|
5
|
+
test("parses markdown to HTML", async () => {
|
|
6
|
+
const engine = createMDConfig();
|
|
7
|
+
const result = await engine.parse("=<h1>Hello=</h1>\n\nWorld");
|
|
8
|
+
expect(result.html.trim()).toBe('<h1 id="hello">Hello</h1><p>World</p>');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("extracts `=<Header>` metadata block and strips it from body HTML", async () => {
|
|
12
|
+
const engine = createMDConfig();
|
|
13
|
+
const md = `=<Header>
|
|
14
|
+
title: My Title
|
|
15
|
+
featured: true
|
|
16
|
+
score: 9.8
|
|
17
|
+
=</Header>
|
|
18
|
+
=<h1>Content=</h1>`;
|
|
19
|
+
const result = await engine.parse(md);
|
|
20
|
+
expect(result.meta).toEqual({
|
|
21
|
+
title: "My Title",
|
|
22
|
+
featured: true,
|
|
23
|
+
score: 9.8,
|
|
24
|
+
});
|
|
25
|
+
expect(result.html.trim()).toBe('<h1 id="content">Content</h1>');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("extracts custom heading tags for table of contents", async () => {
|
|
29
|
+
const engine = createMDConfig();
|
|
30
|
+
const md = `=<h1>Title=</h1>\n\nSome text\n\n=<h2>Subtitle=</h2>\n\n=<h3>Detailed Subtitle=</h3>`;
|
|
31
|
+
const result = await engine.parse(md);
|
|
32
|
+
expect(result.headings).toEqual([
|
|
33
|
+
{ level: 1, text: "Title", id: "title" },
|
|
34
|
+
{ level: 2, text: "Subtitle", id: "subtitle" },
|
|
35
|
+
{ level: 3, text: "Detailed Subtitle", id: "detailed-subtitle" },
|
|
36
|
+
]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("injects copy code button", async () => {
|
|
40
|
+
const engine = createMDConfig({
|
|
41
|
+
features: {
|
|
42
|
+
copyCodeButton: true,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
const result = await engine.parse("```ts\nconst a = 1;\n```");
|
|
46
|
+
expect(result.html).toContain('class="copy-code-btn"');
|
|
47
|
+
expect(result.html).toContain('navigator.clipboard.writeText');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("renders default Callout component tag", async () => {
|
|
51
|
+
const engine = createMDConfig();
|
|
52
|
+
const result = await engine.parse("=<Callout type=\"tip\" title=\"Pro Tip\">\nHello Component\n=</Callout>");
|
|
53
|
+
expect(result.html.trim()).toContain('<div class="callout callout-tip">');
|
|
54
|
+
expect(result.html.trim()).toContain('<div class="callout-title">Pro Tip</div>');
|
|
55
|
+
expect(result.html.trim()).toContain('<p>Hello Component</p>');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("renders default collapsible Callout component tag using details/summary", async () => {
|
|
59
|
+
const engine = createMDConfig();
|
|
60
|
+
const result = await engine.parse("=<Callout type=\"warning\" title=\"Collapse Me\" collapsible=\"true\">\nCollapsible content\n=</Callout>");
|
|
61
|
+
expect(result.html.trim()).toContain('<details class="callout callout-warning" open>');
|
|
62
|
+
expect(result.html.trim()).toContain('<summary class="callout-title">Collapse Me</summary>');
|
|
63
|
+
expect(result.html.trim()).toContain('<p>Collapsible content</p>');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("allows custom components to override default Callout component tag", async () => {
|
|
67
|
+
const engine = createMDConfig({
|
|
68
|
+
components: {
|
|
69
|
+
Callout: (props, rawContent, parse) => {
|
|
70
|
+
return `<section class="custom-user-callout"><h4>${props.title}</h4></section>`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
const result = await engine.parse("=<Callout title=\"Custom Overridden Callout\">\nContent\n=</Callout>");
|
|
75
|
+
expect(result.html.trim()).toBe('<section class="custom-user-callout"><h4>Custom Overridden Callout</h4></section>');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("merges custom class names onto Callout and Tabs components", async () => {
|
|
79
|
+
const engine = createMDConfig();
|
|
80
|
+
const md = `=<Callout type="note" class="my-custom-callout-class">
|
|
81
|
+
Hello
|
|
82
|
+
=</Callout>
|
|
83
|
+
=<Tabs class="my-custom-tabs-class">
|
|
84
|
+
=<Tab title="Tab 1" class="my-custom-tab-panel-class">
|
|
85
|
+
Tab Content
|
|
86
|
+
=</Tab>
|
|
87
|
+
=</Tabs>`;
|
|
88
|
+
const result = await engine.parse(md);
|
|
89
|
+
expect(result.html.trim()).toContain('class="callout callout-note my-custom-callout-class"');
|
|
90
|
+
expect(result.html.trim()).toContain('class="tabs-container my-custom-tabs-class"');
|
|
91
|
+
expect(result.html.trim()).toContain('class="tab-panel my-custom-tab-panel-class active"');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("forwards custom HTML attributes style and colspan on Table tags", async () => {
|
|
95
|
+
const engine = createMDConfig();
|
|
96
|
+
const md = `=<Table style="color: red;">
|
|
97
|
+
=<Row class="my-row-class">
|
|
98
|
+
=<Column colspan="2">Double Header=</Column>
|
|
99
|
+
=</Row>
|
|
100
|
+
=</Table>`;
|
|
101
|
+
const result = await engine.parse(md);
|
|
102
|
+
expect(result.html.trim()).toContain('<table class="auwla-table" style="color: red;">');
|
|
103
|
+
expect(result.html.trim()).toContain('<tr class="my-row-class">');
|
|
104
|
+
expect(result.html.trim()).toContain('<th colspan="2">Double Header</th>');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("injects header anchors to custom heading tags", async () => {
|
|
108
|
+
const engine = createMDConfig({
|
|
109
|
+
features: {
|
|
110
|
+
headerAnchors: true,
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
const result = await engine.parse("=<h1>Getting Started=</h1>");
|
|
114
|
+
expect(result.html.trim()).toBe('<h1 id="getting-started"><a href="#getting-started" class="header-anchor" aria-hidden="true">#</a>Getting Started</h1>');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("renders default Tabs & Tab component tags", async () => {
|
|
118
|
+
const engine = createMDConfig();
|
|
119
|
+
const md = `=<Tabs>
|
|
120
|
+
=<Tab title=\"Panel 1\">
|
|
121
|
+
Content 1
|
|
122
|
+
=</Tab>
|
|
123
|
+
=<Tab title=\"Panel 2\">
|
|
124
|
+
Content 2
|
|
125
|
+
=</Tab>
|
|
126
|
+
=</Tabs>`;
|
|
127
|
+
|
|
128
|
+
const result = await engine.parse(md);
|
|
129
|
+
expect(result.html.trim()).toContain('<div class="tabs-container">');
|
|
130
|
+
expect(result.html.trim()).toContain('Panel 1');
|
|
131
|
+
expect(result.html.trim()).toContain('Panel 2');
|
|
132
|
+
expect(result.html.trim()).toContain('Content 1');
|
|
133
|
+
expect(result.html.trim()).toContain('Content 2');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("renders custom registered component tags", async () => {
|
|
137
|
+
const engine = createMDConfig({
|
|
138
|
+
components: {
|
|
139
|
+
MyCard: (props, rawContent, parse) => {
|
|
140
|
+
return `<div class="card ${props.theme || 'light'}"><h3>${props.title}</h3><div class="body">${rawContent.trim()}</div></div>`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const result = await engine.parse("=<MyCard title=\"Super Card\" theme=\"dark\">\nThis is raw content\n=</MyCard>");
|
|
146
|
+
expect(result.html.trim()).toBe('<div class="card dark"><h3>Super Card</h3><div class="body">This is raw content</div></div>');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("fallback parsing for unregistered standard HTML tags", async () => {
|
|
150
|
+
const engine = createMDConfig();
|
|
151
|
+
const result = await engine.parse("=<h1 class=\"main-title\">\nHello *World*\n=</h1>");
|
|
152
|
+
// Standard h1 tag will get parsed children inline, avoiding double h1/p nesting
|
|
153
|
+
expect(result.html.trim()).toBe('<h1 id="hello-world" class="main-title">Hello <em>World</em></h1>');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("renders built-in Table components", async () => {
|
|
157
|
+
const engine = createMDConfig();
|
|
158
|
+
const md = `=<Table>
|
|
159
|
+
=<Row>
|
|
160
|
+
=<Column>Name=</Column>
|
|
161
|
+
=<Column>Role=</Column>
|
|
162
|
+
=</Row>
|
|
163
|
+
=<Row>
|
|
164
|
+
=<Cell>Alice=</Cell>
|
|
165
|
+
=<Cell>Admin=</Cell>
|
|
166
|
+
=</Row>
|
|
167
|
+
=</Table>`;
|
|
168
|
+
const result = await engine.parse(md);
|
|
169
|
+
expect(result.html.trim()).toContain('<table class="auwla-table">');
|
|
170
|
+
expect(result.html.trim()).toContain('<tr><th>Name</th>\n<th>Role</th></tr>');
|
|
171
|
+
expect(result.html.trim()).toContain('<tr><td>Alice</td>\n<td>Admin</td></tr>');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
=<Header>
|
|
2
|
+
title: Premium Documentation Guide
|
|
3
|
+
author: Amihere Theophilus Junior
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
draft: false
|
|
6
|
+
=</Header>
|
|
7
|
+
|
|
8
|
+
=<h1 class="main-doc-title">Getting Started with Auwla Markdown=</h1>
|
|
9
|
+
|
|
10
|
+
Welcome to the premium documentation page compiled dynamically at build time!
|
|
11
|
+
|
|
12
|
+
Here is a list of features supported by the `@auwla/markdown` engine.
|
|
13
|
+
|
|
14
|
+
=<h2>Built-in UI Elements=</h2>
|
|
15
|
+
|
|
16
|
+
=<h3>1. Interactive Callout Panels=</h3>
|
|
17
|
+
We support visual callout cards for highlight categories:
|
|
18
|
+
|
|
19
|
+
=<Callout type="note">
|
|
20
|
+
This is a standard informational note block. It is rendered using standard paragraphs and can contain **bold** or *italic* markdown tags.
|
|
21
|
+
=</Callout>
|
|
22
|
+
|
|
23
|
+
=<Callout type="tip" title="Compiler-First Tip" collapsible>
|
|
24
|
+
Here is a compiler-first tip! Always remember that wrapping your pages in a single root tag yields highly optimized DOM updates.
|
|
25
|
+
=</Callout>
|
|
26
|
+
|
|
27
|
+
=<Callout type="warning" title="Danger Zone">
|
|
28
|
+
Warning! Do not edit compiled output assets directly inside the `dist/` directory, as they are overwritten on each build.
|
|
29
|
+
=</Callout>
|
|
30
|
+
|
|
31
|
+
=<h3>2. Tab Selection Panels=</h3>
|
|
32
|
+
Tab sections can toggle between multiple content blocks (e.g. installation options or code syntaxes) using native, zero-dependency client togglers:
|
|
33
|
+
|
|
34
|
+
=<Tabs>
|
|
35
|
+
=<Tab title="TSX Component">
|
|
36
|
+
```tsx [Counter.tsx] {7-9}
|
|
37
|
+
import { reactive } from 'auwla';
|
|
38
|
+
|
|
39
|
+
export default function Counter() {
|
|
40
|
+
const count = reactive(0);
|
|
41
|
+
|
|
42
|
+
return () => (
|
|
43
|
+
<button onClick={() => count.set(c => c + 1)}>
|
|
44
|
+
Count is: {count.get()}
|
|
45
|
+
</button>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
=</Tab>
|
|
50
|
+
|
|
51
|
+
=<Tab title="TypeScript">
|
|
52
|
+
```ts [auth.ts] {2,7}
|
|
53
|
+
interface User {
|
|
54
|
+
id: string;
|
|
55
|
+
role: 'admin' | 'user';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isAdmin(user: User): boolean {
|
|
59
|
+
return user.role === 'admin';
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
=</Tab>
|
|
63
|
+
|
|
64
|
+
=<Tab title="Bash CLI">
|
|
65
|
+
```bash
|
|
66
|
+
bun install @auwla/markdown
|
|
67
|
+
```
|
|
68
|
+
=</Tab>
|
|
69
|
+
=</Tabs>
|
|
70
|
+
|
|
71
|
+
=<h2>Component Tables=</h2>
|
|
72
|
+
|
|
73
|
+
Below is a component-driven Table rendered using `=<Table>` and `=<Row>` tags:
|
|
74
|
+
|
|
75
|
+
=<Table border="1">
|
|
76
|
+
=<Row>
|
|
77
|
+
=<Column>Framework=</Column>
|
|
78
|
+
=<Column>Size=</Column>
|
|
79
|
+
=</Row>
|
|
80
|
+
=<Row>
|
|
81
|
+
=<Cell>**Auwla**=</Cell>
|
|
82
|
+
=<Cell>~7 kB=</Cell>
|
|
83
|
+
=</Row>
|
|
84
|
+
=<Row>
|
|
85
|
+
=<Cell>**Next.js**=</Cell>
|
|
86
|
+
=<Cell>~100 kB=</Cell>
|
|
87
|
+
=</Row>
|
|
88
|
+
=</Table>
|
|
89
|
+
|
|
90
|
+
=<h2>Stress-Test Scenarios=</h2>
|
|
91
|
+
|
|
92
|
+
Below we test edge cases, deep nesting, and complex attribute combinations.
|
|
93
|
+
|
|
94
|
+
=<h3>A. Deeply Nested Component Architecture=</h3>
|
|
95
|
+
|
|
96
|
+
Here is a collapsible Callout containing a Tab layout, which inside one of its tab panels contains another nested collapsible Callout and a custom Table:
|
|
97
|
+
|
|
98
|
+
=<Callout type="note" title="Outer Container Callout" collapsible class="outer-class" style="border: 2px dashed #3b82f6; padding: 1.5rem;">
|
|
99
|
+
This is the outer callout card. Below is the nested tabs layout:
|
|
100
|
+
|
|
101
|
+
=<Tabs class="nested-tabs">
|
|
102
|
+
=<Tab title="Overview Panel" class="overview-tab">
|
|
103
|
+
#### Overview of Nested Elements
|
|
104
|
+
|
|
105
|
+
Here is an inner Callout nested inside the tab panel:
|
|
106
|
+
|
|
107
|
+
=<Callout type="tip" title="Inner Nested Callout" collapsible collapsed="true" class="inner-tip-class" style="background-color: #1e293b; border: 1px solid #10b981;">
|
|
108
|
+
This callout is inside a tab panel, which is inside an outer callout.
|
|
109
|
+
|
|
110
|
+
* Nested list item 1
|
|
111
|
+
* Nested list item 2
|
|
112
|
+
=</Callout>
|
|
113
|
+
=</Tab>
|
|
114
|
+
|
|
115
|
+
=<Tab title="Advanced Table Panel">
|
|
116
|
+
Here is a Table nested inside the second tab panel, with custom cell properties:
|
|
117
|
+
|
|
118
|
+
=<Table class="nested-table" style="color: #cbd5e1; border-color: #475569;">
|
|
119
|
+
=<Row class="header-row">
|
|
120
|
+
=<Column colspan="2" style="background-color: #0f172a;">Nested Table Header (Span 2 Columns)=</Column>
|
|
121
|
+
=</Row>
|
|
122
|
+
=<Row>
|
|
123
|
+
=<Cell class="feature-cell">Feature Name=</Cell>
|
|
124
|
+
=<Cell class="value-cell">**SSG Compilation**=</Cell>
|
|
125
|
+
=</Row>
|
|
126
|
+
=<Row>
|
|
127
|
+
=<Cell>Performance=</Cell>
|
|
128
|
+
=<Cell>`100/100` Lighthouse score=</Cell>
|
|
129
|
+
=</Row>
|
|
130
|
+
=</Table>
|
|
131
|
+
=</Tab>
|
|
132
|
+
=</Tabs>
|
|
133
|
+
=</Callout>
|
|
134
|
+
|
|
135
|
+
=<h3>B. Self-Closing Tags & Spaces Tolerance=</h3>
|
|
136
|
+
|
|
137
|
+
We test empty, self-closing, and single-line elements:
|
|
138
|
+
|
|
139
|
+
=<Callout type="caution" title="Empty Self-Closing Callout" />
|
|
140
|
+
|
|
141
|
+
=<Table border="1" class="empty-table-test">
|
|
142
|
+
=<Row>
|
|
143
|
+
=<Column>Col 1=</Column>
|
|
144
|
+
=<Column>Col 2=</Column>
|
|
145
|
+
=</Row>
|
|
146
|
+
=<Row>
|
|
147
|
+
=<Cell>=</Cell>
|
|
148
|
+
=<Cell>=</Cell>
|
|
149
|
+
=</Row>
|
|
150
|
+
=</Table>
|
|
151
|
+
|
|
152
|
+
Testing space tolerance and quotes combinations in tag attributes:
|
|
153
|
+
|
|
154
|
+
=<Callout type = "tip" title = 'Space Tolerant Attribute Matching' collapsible >
|
|
155
|
+
This callout tests spaces around equals and quotes.
|
|
156
|
+
=</Callout>
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
Enjoy building documentation with Auwla!
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { createMDConfig, shikiHighlighter } from '../src/index';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
|
|
5
|
+
async function runLiveTest() {
|
|
6
|
+
const currentDir = import.meta.dir;
|
|
7
|
+
const fixturePath = path.resolve(currentDir, 'fixtures', 'sample.md');
|
|
8
|
+
const outputDir = path.resolve(currentDir, 'output');
|
|
9
|
+
|
|
10
|
+
console.log(`[live-test] Loading test fixture from: ${fixturePath}`);
|
|
11
|
+
|
|
12
|
+
if (!fs.existsSync(fixturePath)) {
|
|
13
|
+
console.error(`[live-test] Error: Fixture sample.md not found!`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const rawMarkdown = fs.readFileSync(fixturePath, 'utf-8');
|
|
18
|
+
|
|
19
|
+
// Create the engine with all features enabled
|
|
20
|
+
const engine = createMDConfig({
|
|
21
|
+
highlighter: shikiHighlighter({
|
|
22
|
+
theme: 'github-dark',
|
|
23
|
+
langs: ['typescript', 'tsx', 'javascript', 'jsx', 'bash', 'html', 'css', 'rust']
|
|
24
|
+
}),
|
|
25
|
+
features: {
|
|
26
|
+
copyCodeButton: true,
|
|
27
|
+
headerAnchors: true
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
console.log('[live-test] Compiling markdown document...');
|
|
32
|
+
const result = await engine.parse(rawMarkdown);
|
|
33
|
+
|
|
34
|
+
// Ensure output directory exists
|
|
35
|
+
if (!fs.existsSync(outputDir)) {
|
|
36
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const htmlOutputPath = path.resolve(outputDir, 'sample.html');
|
|
40
|
+
const jsonOutputPath = path.resolve(outputDir, 'sample.json');
|
|
41
|
+
|
|
42
|
+
// Inject a stylesheet and structural layout in the output HTML so it's readable
|
|
43
|
+
const completeHtml = `<!doctype html>
|
|
44
|
+
<html lang="en">
|
|
45
|
+
<head>
|
|
46
|
+
<meta charset="UTF-8">
|
|
47
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
48
|
+
<title>Auwla Markdown Live Output</title>
|
|
49
|
+
<style>
|
|
50
|
+
body {
|
|
51
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
52
|
+
line-height: 1.6;
|
|
53
|
+
color: #e2e8f0;
|
|
54
|
+
background-color: #0f172a;
|
|
55
|
+
max-width: 800px;
|
|
56
|
+
margin: 2rem auto;
|
|
57
|
+
padding: 0 1rem;
|
|
58
|
+
}
|
|
59
|
+
h1, h2, h3 {
|
|
60
|
+
color: #f1f5f9;
|
|
61
|
+
border-bottom: 1px solid #334155;
|
|
62
|
+
padding-bottom: 0.3rem;
|
|
63
|
+
}
|
|
64
|
+
.header-anchor {
|
|
65
|
+
color: #64748b;
|
|
66
|
+
text-decoration: none;
|
|
67
|
+
margin-right: 0.5rem;
|
|
68
|
+
opacity: 0.5;
|
|
69
|
+
}
|
|
70
|
+
.header-anchor:hover {
|
|
71
|
+
opacity: 1;
|
|
72
|
+
}
|
|
73
|
+
.callout {
|
|
74
|
+
margin: 1.5rem 0;
|
|
75
|
+
padding: 1rem;
|
|
76
|
+
border-left: 4px solid;
|
|
77
|
+
border-radius: 0.25rem;
|
|
78
|
+
background-color: #1e293b;
|
|
79
|
+
}
|
|
80
|
+
.callout-note { border-left-color: #3b82f6; }
|
|
81
|
+
.callout-tip { border-left-color: #10b981; }
|
|
82
|
+
.callout-warning { border-left-color: #f59e0b; }
|
|
83
|
+
.callout-title, details.callout summary {
|
|
84
|
+
font-weight: 700;
|
|
85
|
+
margin-bottom: 0.5rem;
|
|
86
|
+
font-size: 0.875rem;
|
|
87
|
+
letter-spacing: 0.05em;
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
outline: none;
|
|
90
|
+
user-select: none;
|
|
91
|
+
}
|
|
92
|
+
details.callout summary:hover {
|
|
93
|
+
color: #3b82f6;
|
|
94
|
+
}
|
|
95
|
+
.tabs-container {
|
|
96
|
+
margin: 1.5rem 0;
|
|
97
|
+
border: 1px solid #334155;
|
|
98
|
+
border-radius: 0.5rem;
|
|
99
|
+
background-color: #24292e;
|
|
100
|
+
overflow: hidden;
|
|
101
|
+
}
|
|
102
|
+
.tabs-header {
|
|
103
|
+
display: flex;
|
|
104
|
+
background-color: #1b1f23;
|
|
105
|
+
border-bottom: 1px solid #334155;
|
|
106
|
+
}
|
|
107
|
+
.tab-btn {
|
|
108
|
+
padding: 0.75rem 1.25rem;
|
|
109
|
+
border: none;
|
|
110
|
+
background: none;
|
|
111
|
+
color: #94a3b8;
|
|
112
|
+
font-weight: 500;
|
|
113
|
+
cursor: pointer;
|
|
114
|
+
border-bottom: 2px solid transparent;
|
|
115
|
+
transition: all 0.2s;
|
|
116
|
+
}
|
|
117
|
+
.tab-btn:hover {
|
|
118
|
+
color: #f1f5f9;
|
|
119
|
+
background-color: #24292e;
|
|
120
|
+
}
|
|
121
|
+
.tab-btn.active {
|
|
122
|
+
color: #3b82f6;
|
|
123
|
+
border-bottom-color: #3b82f6;
|
|
124
|
+
background-color: #24292e;
|
|
125
|
+
}
|
|
126
|
+
.tab-panel {
|
|
127
|
+
padding: 0;
|
|
128
|
+
}
|
|
129
|
+
.tab-panel .code-block-wrapper pre {
|
|
130
|
+
margin: 0;
|
|
131
|
+
border: none;
|
|
132
|
+
border-radius: 0;
|
|
133
|
+
}
|
|
134
|
+
.code-block-filename {
|
|
135
|
+
background-color: #1b1f23;
|
|
136
|
+
color: #94a3b8;
|
|
137
|
+
padding: 0.5rem 1rem;
|
|
138
|
+
font-size: 0.8rem;
|
|
139
|
+
font-weight: 500;
|
|
140
|
+
border-bottom: 1px solid #334155;
|
|
141
|
+
font-family: Consolas, Monaco, monospace;
|
|
142
|
+
}
|
|
143
|
+
.highlighted-line {
|
|
144
|
+
background-color: #2e3b4e;
|
|
145
|
+
display: block;
|
|
146
|
+
margin-left: -1rem;
|
|
147
|
+
margin-right: -1rem;
|
|
148
|
+
padding-left: 0.8rem;
|
|
149
|
+
padding-right: 1rem;
|
|
150
|
+
border-left: 4px solid #3b82f6;
|
|
151
|
+
}
|
|
152
|
+
pre {
|
|
153
|
+
background-color: #0f172a;
|
|
154
|
+
padding: 1rem;
|
|
155
|
+
border-radius: 0.375rem;
|
|
156
|
+
overflow-x: auto;
|
|
157
|
+
border: 1px solid #334155;
|
|
158
|
+
}
|
|
159
|
+
code {
|
|
160
|
+
font-family: Consolas, Monaco, "Andale Mono", monospace;
|
|
161
|
+
color: #e2e8f0;
|
|
162
|
+
}
|
|
163
|
+
.copy-code-btn {
|
|
164
|
+
background-color: #334155;
|
|
165
|
+
color: #f1f5f9;
|
|
166
|
+
border: none;
|
|
167
|
+
padding: 0.25rem 0.5rem;
|
|
168
|
+
font-size: 0.75rem;
|
|
169
|
+
border-radius: 0.25rem;
|
|
170
|
+
cursor: pointer;
|
|
171
|
+
transition: background-color 0.2s;
|
|
172
|
+
}
|
|
173
|
+
.copy-code-btn:hover {
|
|
174
|
+
background-color: #475569;
|
|
175
|
+
}
|
|
176
|
+
.auwla-header {
|
|
177
|
+
background-color: #1e293b;
|
|
178
|
+
border: 1px solid #334155;
|
|
179
|
+
padding: 1rem 1.5rem;
|
|
180
|
+
margin-bottom: 2rem;
|
|
181
|
+
border-radius: 0.5rem;
|
|
182
|
+
display: flex;
|
|
183
|
+
align-items: center;
|
|
184
|
+
}
|
|
185
|
+
.auwla-header span {
|
|
186
|
+
font-size: 1.25rem;
|
|
187
|
+
font-weight: 700;
|
|
188
|
+
color: #f8fafc;
|
|
189
|
+
}
|
|
190
|
+
.auwla-table {
|
|
191
|
+
width: 100%;
|
|
192
|
+
border-collapse: collapse;
|
|
193
|
+
margin: 1.5rem 0;
|
|
194
|
+
border: 1px solid #334155;
|
|
195
|
+
border-radius: 0.5rem;
|
|
196
|
+
overflow: hidden;
|
|
197
|
+
}
|
|
198
|
+
.auwla-table th {
|
|
199
|
+
background-color: #1b1f23;
|
|
200
|
+
color: #f1f5f9;
|
|
201
|
+
padding: 0.75rem 1rem;
|
|
202
|
+
font-weight: 600;
|
|
203
|
+
border-bottom: 1px solid #334155;
|
|
204
|
+
}
|
|
205
|
+
.auwla-table td {
|
|
206
|
+
padding: 0.75rem 1rem;
|
|
207
|
+
color: #cbd5e1;
|
|
208
|
+
border-bottom: 1px solid #334155;
|
|
209
|
+
}
|
|
210
|
+
.auwla-table tr:last-child td {
|
|
211
|
+
border-bottom: none;
|
|
212
|
+
}
|
|
213
|
+
</style>
|
|
214
|
+
</head>
|
|
215
|
+
<body>
|
|
216
|
+
<div style="margin-bottom: 2rem;">
|
|
217
|
+
<a href="./sample.json" target="_blank" style="color: #3b82f6; text-decoration: none;">View Parsed JSON Metadata (TOC & Frontmatter)</a>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<!-- Compiled Output -->
|
|
221
|
+
${result.html}
|
|
222
|
+
</body>
|
|
223
|
+
</html>
|
|
224
|
+
`;
|
|
225
|
+
|
|
226
|
+
fs.writeFileSync(htmlOutputPath, completeHtml, 'utf-8');
|
|
227
|
+
|
|
228
|
+
const metaOutput = {
|
|
229
|
+
meta: result.meta,
|
|
230
|
+
headings: result.headings
|
|
231
|
+
};
|
|
232
|
+
fs.writeFileSync(jsonOutputPath, JSON.stringify(metaOutput, null, 2), 'utf-8');
|
|
233
|
+
|
|
234
|
+
console.log(`[live-test] Success! Compiled files generated successfully:`);
|
|
235
|
+
console.log(` - HTML output: ${htmlOutputPath}`);
|
|
236
|
+
console.log(` - JSON metadata: ${jsonOutputPath}`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
runLiveTest().catch(console.error);
|