@ox-content/vite-plugin 0.0.1-alpha.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/dist/index.cjs +2515 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +811 -0
- package/dist/index.d.ts +811 -0
- package/dist/index.js +2468 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,811 @@
|
|
|
1
|
+
import { EnvironmentOptions, Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Type definitions for vite-plugin-ox-content
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* SSG (Static Site Generation) options.
|
|
8
|
+
*/
|
|
9
|
+
interface SsgOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Enable SSG mode.
|
|
12
|
+
* @default true
|
|
13
|
+
*/
|
|
14
|
+
enabled?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Output file extension.
|
|
17
|
+
* @default '.html'
|
|
18
|
+
*/
|
|
19
|
+
extension?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Clean output directory before build.
|
|
22
|
+
* @default false
|
|
23
|
+
*/
|
|
24
|
+
clean?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Bare HTML output (no navigation, no styles).
|
|
27
|
+
* Useful for benchmarking or when using custom layouts.
|
|
28
|
+
* @default false
|
|
29
|
+
*/
|
|
30
|
+
bare?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Site name for header and title suffix.
|
|
33
|
+
*/
|
|
34
|
+
siteName?: string;
|
|
35
|
+
/**
|
|
36
|
+
* OG image URL for social sharing (static URL).
|
|
37
|
+
* If generateOgImage is enabled, this serves as the fallback.
|
|
38
|
+
*/
|
|
39
|
+
ogImage?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Generate OG images per page using Rust-based generator.
|
|
42
|
+
* When enabled, each page will have a unique OG image.
|
|
43
|
+
* @default false
|
|
44
|
+
*/
|
|
45
|
+
generateOgImage?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Site URL for generating absolute OG image URLs.
|
|
48
|
+
* Required for proper SNS sharing.
|
|
49
|
+
* Example: 'https://example.com'
|
|
50
|
+
*/
|
|
51
|
+
siteUrl?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolved SSG options.
|
|
55
|
+
*/
|
|
56
|
+
interface ResolvedSsgOptions {
|
|
57
|
+
enabled: boolean;
|
|
58
|
+
extension: string;
|
|
59
|
+
clean: boolean;
|
|
60
|
+
bare: boolean;
|
|
61
|
+
siteName?: string;
|
|
62
|
+
ogImage?: string;
|
|
63
|
+
generateOgImage: boolean;
|
|
64
|
+
siteUrl?: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Plugin options.
|
|
68
|
+
*/
|
|
69
|
+
interface OxContentOptions {
|
|
70
|
+
/**
|
|
71
|
+
* Source directory for Markdown files.
|
|
72
|
+
* @default 'docs'
|
|
73
|
+
*/
|
|
74
|
+
srcDir?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Output directory for built files.
|
|
77
|
+
* @default 'dist'
|
|
78
|
+
*/
|
|
79
|
+
outDir?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Base path for the site.
|
|
82
|
+
* @default '/'
|
|
83
|
+
*/
|
|
84
|
+
base?: string;
|
|
85
|
+
/**
|
|
86
|
+
* SSG (Static Site Generation) options.
|
|
87
|
+
* Set to false to disable SSG completely.
|
|
88
|
+
* @default { enabled: true }
|
|
89
|
+
*/
|
|
90
|
+
ssg?: SsgOptions | boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Enable GitHub Flavored Markdown extensions.
|
|
93
|
+
* @default true
|
|
94
|
+
*/
|
|
95
|
+
gfm?: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Enable footnotes.
|
|
98
|
+
* @default true
|
|
99
|
+
*/
|
|
100
|
+
footnotes?: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Enable tables.
|
|
103
|
+
* @default true
|
|
104
|
+
*/
|
|
105
|
+
tables?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Enable task lists.
|
|
108
|
+
* @default true
|
|
109
|
+
*/
|
|
110
|
+
taskLists?: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Enable strikethrough.
|
|
113
|
+
* @default true
|
|
114
|
+
*/
|
|
115
|
+
strikethrough?: boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Enable syntax highlighting for code blocks.
|
|
118
|
+
* @default false
|
|
119
|
+
*/
|
|
120
|
+
highlight?: boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Syntax highlighting theme.
|
|
123
|
+
* @default 'github-dark'
|
|
124
|
+
*/
|
|
125
|
+
highlightTheme?: string;
|
|
126
|
+
/**
|
|
127
|
+
* Enable mermaid diagram rendering.
|
|
128
|
+
* @default false
|
|
129
|
+
*/
|
|
130
|
+
mermaid?: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Parse YAML frontmatter.
|
|
133
|
+
* @default true
|
|
134
|
+
*/
|
|
135
|
+
frontmatter?: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Generate table of contents.
|
|
138
|
+
* @default true
|
|
139
|
+
*/
|
|
140
|
+
toc?: boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Maximum heading depth for TOC.
|
|
143
|
+
* @default 3
|
|
144
|
+
*/
|
|
145
|
+
tocMaxDepth?: number;
|
|
146
|
+
/**
|
|
147
|
+
* Enable OG image generation.
|
|
148
|
+
* @default false
|
|
149
|
+
*/
|
|
150
|
+
ogImage?: boolean;
|
|
151
|
+
/**
|
|
152
|
+
* OG image generation options.
|
|
153
|
+
*/
|
|
154
|
+
ogImageOptions?: OgImageOptions;
|
|
155
|
+
/**
|
|
156
|
+
* Custom AST transformers.
|
|
157
|
+
*/
|
|
158
|
+
transformers?: MarkdownTransformer[];
|
|
159
|
+
/**
|
|
160
|
+
* Source documentation generation options.
|
|
161
|
+
* Set to false to disable (opt-out).
|
|
162
|
+
* @default { enabled: true }
|
|
163
|
+
*/
|
|
164
|
+
docs?: DocsOptions | false;
|
|
165
|
+
/**
|
|
166
|
+
* Full-text search options.
|
|
167
|
+
* Set to false to disable search.
|
|
168
|
+
* @default { enabled: true }
|
|
169
|
+
*/
|
|
170
|
+
search?: SearchOptions | boolean;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Resolved options with all defaults applied.
|
|
174
|
+
*/
|
|
175
|
+
interface ResolvedOptions {
|
|
176
|
+
srcDir: string;
|
|
177
|
+
outDir: string;
|
|
178
|
+
base: string;
|
|
179
|
+
ssg: ResolvedSsgOptions;
|
|
180
|
+
gfm: boolean;
|
|
181
|
+
footnotes: boolean;
|
|
182
|
+
tables: boolean;
|
|
183
|
+
taskLists: boolean;
|
|
184
|
+
strikethrough: boolean;
|
|
185
|
+
highlight: boolean;
|
|
186
|
+
highlightTheme: string;
|
|
187
|
+
mermaid: boolean;
|
|
188
|
+
frontmatter: boolean;
|
|
189
|
+
toc: boolean;
|
|
190
|
+
tocMaxDepth: number;
|
|
191
|
+
ogImage: boolean;
|
|
192
|
+
ogImageOptions: OgImageOptions;
|
|
193
|
+
transformers: MarkdownTransformer[];
|
|
194
|
+
docs: ResolvedDocsOptions | false;
|
|
195
|
+
search: ResolvedSearchOptions;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* OG image generation options.
|
|
199
|
+
*/
|
|
200
|
+
interface OgImageOptions {
|
|
201
|
+
/**
|
|
202
|
+
* Background color.
|
|
203
|
+
* @default '#1a1a2e'
|
|
204
|
+
*/
|
|
205
|
+
background?: string;
|
|
206
|
+
/**
|
|
207
|
+
* Text color.
|
|
208
|
+
* @default '#ffffff'
|
|
209
|
+
*/
|
|
210
|
+
textColor?: string;
|
|
211
|
+
/**
|
|
212
|
+
* Accent color.
|
|
213
|
+
* @default '#e94560'
|
|
214
|
+
*/
|
|
215
|
+
accentColor?: string;
|
|
216
|
+
/**
|
|
217
|
+
* Font family.
|
|
218
|
+
*/
|
|
219
|
+
fontFamily?: string;
|
|
220
|
+
/**
|
|
221
|
+
* Image width.
|
|
222
|
+
* @default 1200
|
|
223
|
+
*/
|
|
224
|
+
width?: number;
|
|
225
|
+
/**
|
|
226
|
+
* Image height.
|
|
227
|
+
* @default 630
|
|
228
|
+
*/
|
|
229
|
+
height?: number;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Custom AST transformer.
|
|
233
|
+
*/
|
|
234
|
+
interface MarkdownTransformer {
|
|
235
|
+
/**
|
|
236
|
+
* Transformer name.
|
|
237
|
+
*/
|
|
238
|
+
name: string;
|
|
239
|
+
/**
|
|
240
|
+
* Transform function.
|
|
241
|
+
*/
|
|
242
|
+
transform: (ast: MarkdownNode, context: TransformContext) => MarkdownNode | Promise<MarkdownNode>;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Transform context passed to transformers.
|
|
246
|
+
*/
|
|
247
|
+
interface TransformContext {
|
|
248
|
+
/**
|
|
249
|
+
* File path being processed.
|
|
250
|
+
*/
|
|
251
|
+
filePath: string;
|
|
252
|
+
/**
|
|
253
|
+
* Frontmatter data.
|
|
254
|
+
*/
|
|
255
|
+
frontmatter: Record<string, unknown>;
|
|
256
|
+
/**
|
|
257
|
+
* Resolved plugin options.
|
|
258
|
+
*/
|
|
259
|
+
options: ResolvedOptions;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Markdown AST node (simplified for TypeScript).
|
|
263
|
+
*/
|
|
264
|
+
interface MarkdownNode {
|
|
265
|
+
type: string;
|
|
266
|
+
children?: MarkdownNode[];
|
|
267
|
+
value?: string;
|
|
268
|
+
[key: string]: unknown;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Transform result.
|
|
272
|
+
*/
|
|
273
|
+
interface TransformResult {
|
|
274
|
+
/**
|
|
275
|
+
* Generated JavaScript code.
|
|
276
|
+
*/
|
|
277
|
+
code: string;
|
|
278
|
+
/**
|
|
279
|
+
* Source map (null means no source map).
|
|
280
|
+
*/
|
|
281
|
+
map?: null;
|
|
282
|
+
/**
|
|
283
|
+
* Rendered HTML.
|
|
284
|
+
*/
|
|
285
|
+
html: string;
|
|
286
|
+
/**
|
|
287
|
+
* Parsed frontmatter.
|
|
288
|
+
*/
|
|
289
|
+
frontmatter: Record<string, unknown>;
|
|
290
|
+
/**
|
|
291
|
+
* Table of contents.
|
|
292
|
+
*/
|
|
293
|
+
toc: TocEntry[];
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Table of contents entry.
|
|
297
|
+
*/
|
|
298
|
+
interface TocEntry {
|
|
299
|
+
/**
|
|
300
|
+
* Heading depth (1-6).
|
|
301
|
+
*/
|
|
302
|
+
depth: number;
|
|
303
|
+
/**
|
|
304
|
+
* Heading text.
|
|
305
|
+
*/
|
|
306
|
+
text: string;
|
|
307
|
+
/**
|
|
308
|
+
* Slug/ID for linking.
|
|
309
|
+
*/
|
|
310
|
+
slug: string;
|
|
311
|
+
/**
|
|
312
|
+
* Child entries.
|
|
313
|
+
*/
|
|
314
|
+
children: TocEntry[];
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Options for source documentation generation.
|
|
318
|
+
*/
|
|
319
|
+
interface DocsOptions {
|
|
320
|
+
/**
|
|
321
|
+
* Enable/disable docs generation.
|
|
322
|
+
* @default true (opt-out)
|
|
323
|
+
*/
|
|
324
|
+
enabled?: boolean;
|
|
325
|
+
/**
|
|
326
|
+
* Source directories to scan for documentation.
|
|
327
|
+
* @default ['./src']
|
|
328
|
+
*/
|
|
329
|
+
src?: string[];
|
|
330
|
+
/**
|
|
331
|
+
* Output directory for generated documentation.
|
|
332
|
+
* @default 'docs/api'
|
|
333
|
+
*/
|
|
334
|
+
out?: string;
|
|
335
|
+
/**
|
|
336
|
+
* Glob patterns for files to include.
|
|
337
|
+
* @default ['**\/*.ts', '**\/*.tsx']
|
|
338
|
+
*/
|
|
339
|
+
include?: string[];
|
|
340
|
+
/**
|
|
341
|
+
* Glob patterns for files to exclude.
|
|
342
|
+
* @default ['**\/*.test.*', '**\/*.spec.*', 'node_modules']
|
|
343
|
+
*/
|
|
344
|
+
exclude?: string[];
|
|
345
|
+
/**
|
|
346
|
+
* Output format.
|
|
347
|
+
* @default 'markdown'
|
|
348
|
+
*/
|
|
349
|
+
format?: 'markdown' | 'json' | 'html';
|
|
350
|
+
/**
|
|
351
|
+
* Include private members in documentation.
|
|
352
|
+
* @default false
|
|
353
|
+
*/
|
|
354
|
+
private?: boolean;
|
|
355
|
+
/**
|
|
356
|
+
* Generate table of contents for each file.
|
|
357
|
+
* @default true
|
|
358
|
+
*/
|
|
359
|
+
toc?: boolean;
|
|
360
|
+
/**
|
|
361
|
+
* Group documentation by file or category.
|
|
362
|
+
* @default 'file'
|
|
363
|
+
*/
|
|
364
|
+
groupBy?: 'file' | 'category';
|
|
365
|
+
/**
|
|
366
|
+
* GitHub repository URL for source code links.
|
|
367
|
+
* When provided, generated documentation will include links to source code.
|
|
368
|
+
* Example: 'https://github.com/ubugeeei/ox-content'
|
|
369
|
+
*/
|
|
370
|
+
githubUrl?: string;
|
|
371
|
+
/**
|
|
372
|
+
* Generate navigation metadata file.
|
|
373
|
+
* @default true
|
|
374
|
+
*/
|
|
375
|
+
generateNav?: boolean;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Resolved docs options with all defaults applied.
|
|
379
|
+
*/
|
|
380
|
+
interface ResolvedDocsOptions {
|
|
381
|
+
enabled: boolean;
|
|
382
|
+
src: string[];
|
|
383
|
+
out: string;
|
|
384
|
+
include: string[];
|
|
385
|
+
exclude: string[];
|
|
386
|
+
format: 'markdown' | 'json' | 'html';
|
|
387
|
+
private: boolean;
|
|
388
|
+
toc: boolean;
|
|
389
|
+
groupBy: 'file' | 'category';
|
|
390
|
+
githubUrl?: string;
|
|
391
|
+
generateNav: boolean;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* A single documentation entry extracted from source.
|
|
395
|
+
*/
|
|
396
|
+
interface DocEntry {
|
|
397
|
+
name: string;
|
|
398
|
+
kind: 'function' | 'class' | 'interface' | 'type' | 'variable' | 'module';
|
|
399
|
+
description: string;
|
|
400
|
+
params?: ParamDoc[];
|
|
401
|
+
returns?: ReturnDoc;
|
|
402
|
+
examples?: string[];
|
|
403
|
+
tags?: Record<string, string>;
|
|
404
|
+
private?: boolean;
|
|
405
|
+
file: string;
|
|
406
|
+
line: number;
|
|
407
|
+
signature?: string;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Parameter documentation.
|
|
411
|
+
*/
|
|
412
|
+
interface ParamDoc {
|
|
413
|
+
name: string;
|
|
414
|
+
type: string;
|
|
415
|
+
description: string;
|
|
416
|
+
optional?: boolean;
|
|
417
|
+
default?: string;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Return type documentation.
|
|
421
|
+
*/
|
|
422
|
+
interface ReturnDoc {
|
|
423
|
+
type: string;
|
|
424
|
+
description: string;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Extracted documentation for a single file.
|
|
428
|
+
*/
|
|
429
|
+
interface ExtractedDocs {
|
|
430
|
+
file: string;
|
|
431
|
+
entries: DocEntry[];
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Navigation item for sidebar navigation.
|
|
435
|
+
*/
|
|
436
|
+
interface NavItem {
|
|
437
|
+
/**
|
|
438
|
+
* Display title for the navigation item.
|
|
439
|
+
*/
|
|
440
|
+
title: string;
|
|
441
|
+
/**
|
|
442
|
+
* Path to the documentation page.
|
|
443
|
+
*/
|
|
444
|
+
path: string;
|
|
445
|
+
/**
|
|
446
|
+
* Child navigation items (optional).
|
|
447
|
+
*/
|
|
448
|
+
children?: NavItem[];
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Options for full-text search.
|
|
452
|
+
*/
|
|
453
|
+
interface SearchOptions {
|
|
454
|
+
/**
|
|
455
|
+
* Enable search functionality.
|
|
456
|
+
* @default true
|
|
457
|
+
*/
|
|
458
|
+
enabled?: boolean;
|
|
459
|
+
/**
|
|
460
|
+
* Maximum number of search results.
|
|
461
|
+
* @default 10
|
|
462
|
+
*/
|
|
463
|
+
limit?: number;
|
|
464
|
+
/**
|
|
465
|
+
* Enable prefix matching for autocomplete.
|
|
466
|
+
* @default true
|
|
467
|
+
*/
|
|
468
|
+
prefix?: boolean;
|
|
469
|
+
/**
|
|
470
|
+
* Placeholder text for the search input.
|
|
471
|
+
* @default 'Search documentation...'
|
|
472
|
+
*/
|
|
473
|
+
placeholder?: string;
|
|
474
|
+
/**
|
|
475
|
+
* Keyboard shortcut to focus search (without modifier).
|
|
476
|
+
* @default '/'
|
|
477
|
+
*/
|
|
478
|
+
hotkey?: string;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Resolved search options.
|
|
482
|
+
*/
|
|
483
|
+
interface ResolvedSearchOptions {
|
|
484
|
+
enabled: boolean;
|
|
485
|
+
limit: number;
|
|
486
|
+
prefix: boolean;
|
|
487
|
+
placeholder: string;
|
|
488
|
+
hotkey: string;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Search document structure.
|
|
492
|
+
*/
|
|
493
|
+
interface SearchDocument {
|
|
494
|
+
id: string;
|
|
495
|
+
title: string;
|
|
496
|
+
url: string;
|
|
497
|
+
body: string;
|
|
498
|
+
headings: string[];
|
|
499
|
+
code: string[];
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Search result structure.
|
|
503
|
+
*/
|
|
504
|
+
interface SearchResult {
|
|
505
|
+
id: string;
|
|
506
|
+
title: string;
|
|
507
|
+
url: string;
|
|
508
|
+
score: number;
|
|
509
|
+
matches: string[];
|
|
510
|
+
snippet: string;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Vite Environment API integration for Ox Content.
|
|
515
|
+
*
|
|
516
|
+
* Creates a dedicated environment for Markdown processing,
|
|
517
|
+
* enabling SSG-style rendering with separate client/server contexts.
|
|
518
|
+
*/
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Creates the Markdown processing environment configuration.
|
|
522
|
+
*
|
|
523
|
+
* This environment is used for:
|
|
524
|
+
* - Server-side rendering of Markdown files
|
|
525
|
+
* - Static site generation
|
|
526
|
+
* - Pre-rendering at build time
|
|
527
|
+
*
|
|
528
|
+
* @example
|
|
529
|
+
* ```ts
|
|
530
|
+
* // In your vite.config.ts
|
|
531
|
+
* export default defineConfig({
|
|
532
|
+
* environments: {
|
|
533
|
+
* markdown: createMarkdownEnvironment({
|
|
534
|
+
* srcDir: 'docs',
|
|
535
|
+
* gfm: true,
|
|
536
|
+
* }),
|
|
537
|
+
* },
|
|
538
|
+
* });
|
|
539
|
+
* ```
|
|
540
|
+
*/
|
|
541
|
+
declare function createMarkdownEnvironment(options: ResolvedOptions): EnvironmentOptions;
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Markdown Transformation Engine
|
|
545
|
+
*
|
|
546
|
+
* This module handles the complete transformation pipeline for Markdown files,
|
|
547
|
+
* converting raw Markdown content into JavaScript modules that can be imported
|
|
548
|
+
* by web applications. The transformation process includes:
|
|
549
|
+
*
|
|
550
|
+
* 1. **Parsing**: Uses Rust-based parser via NAPI bindings for high performance
|
|
551
|
+
* 2. **Rendering**: Converts parsed AST to semantic HTML
|
|
552
|
+
* 3. **Enhancement**: Applies syntax highlighting, Mermaid diagram rendering, etc.
|
|
553
|
+
* 4. **Code Generation**: Generates JavaScript/TypeScript module code
|
|
554
|
+
*
|
|
555
|
+
* The generated modules export:
|
|
556
|
+
* - `html`: Rendered HTML content
|
|
557
|
+
* - `frontmatter`: Parsed YAML metadata
|
|
558
|
+
* - `toc`: Hierarchical table of contents
|
|
559
|
+
* - `render`: Client-side render function for dynamic updates
|
|
560
|
+
*
|
|
561
|
+
* @example
|
|
562
|
+
* ```typescript
|
|
563
|
+
* import { transformMarkdown } from './transform';
|
|
564
|
+
*
|
|
565
|
+
* const content = await transformMarkdown(
|
|
566
|
+
* '# Hello\n\nWorld',
|
|
567
|
+
* 'path/to/file.md',
|
|
568
|
+
* resolvedOptions
|
|
569
|
+
* );
|
|
570
|
+
*
|
|
571
|
+
* console.log(content.html); // '<h1>Hello</h1><p>World</p>'
|
|
572
|
+
* console.log(content.toc); // [{ depth: 1, text: 'Hello', slug: 'hello', children: [] }]
|
|
573
|
+
* ```
|
|
574
|
+
*/
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Transforms Markdown content into a JavaScript module.
|
|
578
|
+
*
|
|
579
|
+
* This is the primary entry point for transforming Markdown files. It handles
|
|
580
|
+
* the complete transformation pipeline including parsing, rendering, syntax
|
|
581
|
+
* highlighting, and code generation.
|
|
582
|
+
*
|
|
583
|
+
* ## Pipeline Steps
|
|
584
|
+
*
|
|
585
|
+
* 1. **Parse & Render**: Uses Rust-based parser via NAPI for high performance
|
|
586
|
+
* 2. **Extract Metadata**: Parses YAML frontmatter and generates table of contents
|
|
587
|
+
* 3. **Enhance HTML**: Applies syntax highlighting and Mermaid diagram rendering
|
|
588
|
+
* 4. **Generate Code**: Creates importable JavaScript module
|
|
589
|
+
*
|
|
590
|
+
* ## Generated Module Exports
|
|
591
|
+
*
|
|
592
|
+
* - `html` (string): Rendered HTML content with all enhancements applied
|
|
593
|
+
* - `frontmatter` (object): Parsed YAML frontmatter as JavaScript object
|
|
594
|
+
* - `toc` (array): Hierarchical table of contents entries
|
|
595
|
+
* - `render` (function): Client-side render function for dynamic updates
|
|
596
|
+
*
|
|
597
|
+
* ## Markdown Features Supported
|
|
598
|
+
*
|
|
599
|
+
* The supported features depend on parser options:
|
|
600
|
+
* - **Commonmark**: Headings, paragraphs, lists, code blocks, links, images
|
|
601
|
+
* - **GFM Extensions**: Tables, task lists, strikethrough, autolinks
|
|
602
|
+
* - **Enhancements**: Syntax highlighting, Mermaid diagrams, TOC generation
|
|
603
|
+
* - **Metadata**: YAML frontmatter parsing
|
|
604
|
+
*
|
|
605
|
+
* ## Performance
|
|
606
|
+
*
|
|
607
|
+
* Uses Rust-based parsing via NAPI bindings for optimal performance. Falls back
|
|
608
|
+
* gracefully if Rust bindings are unavailable.
|
|
609
|
+
*
|
|
610
|
+
* @param source - Raw Markdown source code (may include YAML frontmatter)
|
|
611
|
+
* @param filePath - File path for source attribution and relative link resolution
|
|
612
|
+
* @param options - Resolved plugin options controlling transformation behavior
|
|
613
|
+
*
|
|
614
|
+
* @returns Promise resolving to transformation result with HTML and metadata
|
|
615
|
+
*
|
|
616
|
+
* @throws Error if NAPI bindings are unavailable (can be handled gracefully)
|
|
617
|
+
*
|
|
618
|
+
* @example
|
|
619
|
+
* ```typescript
|
|
620
|
+
* import { transformMarkdown } from './transform';
|
|
621
|
+
* import { resolveOptions } from './index';
|
|
622
|
+
*
|
|
623
|
+
* // Transform a Markdown file with YAML frontmatter
|
|
624
|
+
* const markdown = `---
|
|
625
|
+
* title: Getting Started
|
|
626
|
+
* author: john
|
|
627
|
+
* ---
|
|
628
|
+
*
|
|
629
|
+
* # Getting Started
|
|
630
|
+
*
|
|
631
|
+
* Welcome! This guide explains [transformMarkdown] function.
|
|
632
|
+
*
|
|
633
|
+
* ## Installation
|
|
634
|
+
*
|
|
635
|
+
* \`\`\`bash
|
|
636
|
+
* npm install vite-plugin-ox-content
|
|
637
|
+
* \`\`\`
|
|
638
|
+
* `;
|
|
639
|
+
*
|
|
640
|
+
* const options = resolveOptions({
|
|
641
|
+
* highlight: true,
|
|
642
|
+
* highlightTheme: 'github-dark',
|
|
643
|
+
* toc: true,
|
|
644
|
+
* gfm: true,
|
|
645
|
+
* mermaid: true,
|
|
646
|
+
* });
|
|
647
|
+
*
|
|
648
|
+
* const result = await transformMarkdown(markdown, 'docs/getting-started.md', options);
|
|
649
|
+
*
|
|
650
|
+
* // Generated module exports
|
|
651
|
+
* console.log(result.html); // Rendered HTML with syntax highlighting
|
|
652
|
+
* console.log(result.frontmatter); // { title: 'Getting Started', author: 'john' }
|
|
653
|
+
* console.log(result.toc); // [{ depth: 1, text: 'Getting Started', ... }]
|
|
654
|
+
* console.log(result.code); // ES module export statement
|
|
655
|
+
* ```
|
|
656
|
+
*/
|
|
657
|
+
/**
|
|
658
|
+
* SSG-specific transform options.
|
|
659
|
+
*/
|
|
660
|
+
interface SsgTransformOptions {
|
|
661
|
+
/** Convert `.md` links to `.html` links */
|
|
662
|
+
convertMdLinks?: boolean;
|
|
663
|
+
/** Base URL for absolute link conversion */
|
|
664
|
+
baseUrl?: string;
|
|
665
|
+
}
|
|
666
|
+
declare function transformMarkdown(source: string, filePath: string, options: ResolvedOptions, ssgOptions?: SsgTransformOptions): Promise<TransformResult>;
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Extracts JSDoc documentation from source files in specified directories.
|
|
670
|
+
*
|
|
671
|
+
* This function recursively searches directories for source files matching
|
|
672
|
+
* the include/exclude patterns, then extracts all documented items (functions,
|
|
673
|
+
* classes, interfaces, types) from those files.
|
|
674
|
+
*
|
|
675
|
+
* ## Process
|
|
676
|
+
*
|
|
677
|
+
* 1. **File Discovery**: Recursively walks directories, applying filters
|
|
678
|
+
* 2. **File Reading**: Loads each matching file's content
|
|
679
|
+
* 3. **JSDoc Extraction**: Parses JSDoc comments using regex patterns
|
|
680
|
+
* 4. **Declaration Matching**: Pairs JSDoc comments with source declarations
|
|
681
|
+
* 5. **Result Collection**: Aggregates extracted documentation by file
|
|
682
|
+
*
|
|
683
|
+
* ## Include/Exclude Patterns
|
|
684
|
+
*
|
|
685
|
+
* Patterns support:
|
|
686
|
+
* - `**` - Match any directory structure
|
|
687
|
+
* - `*` - Match any filename
|
|
688
|
+
* - Standard glob patterns (e.g., `**\/*.test.ts`)
|
|
689
|
+
*
|
|
690
|
+
* ## Performance Considerations
|
|
691
|
+
*
|
|
692
|
+
* - Uses filesystem I/O which can be slow for large codebases
|
|
693
|
+
* - Consider using more specific include patterns to reduce file scanning
|
|
694
|
+
* - Results are not cached; call once per build/dev session
|
|
695
|
+
*
|
|
696
|
+
* @param srcDirs - Array of source directory paths to scan
|
|
697
|
+
* @param options - Documentation extraction options (filters, grouping, etc.)
|
|
698
|
+
*
|
|
699
|
+
* @returns Promise resolving to array of extracted documentation by file.
|
|
700
|
+
* Each ExtractedDocs object contains file path and array of DocEntry items.
|
|
701
|
+
*
|
|
702
|
+
* @example
|
|
703
|
+
* ```typescript
|
|
704
|
+
* const docs = await extractDocs(
|
|
705
|
+
* ['./packages/vite-plugin/src'],
|
|
706
|
+
* {
|
|
707
|
+
* enabled: true,
|
|
708
|
+
* src: [],
|
|
709
|
+
* out: 'docs',
|
|
710
|
+
* include: ['**\/*.ts'],
|
|
711
|
+
* exclude: ['**\/*.test.ts', '**\/*.spec.ts'],
|
|
712
|
+
* format: 'markdown',
|
|
713
|
+
* private: false,
|
|
714
|
+
* toc: true,
|
|
715
|
+
* groupBy: 'file',
|
|
716
|
+
* generateNav: true,
|
|
717
|
+
* }
|
|
718
|
+
* );
|
|
719
|
+
*
|
|
720
|
+
* // Returns:
|
|
721
|
+
* // [
|
|
722
|
+
* // {
|
|
723
|
+
* // file: '/path/to/transform.ts',
|
|
724
|
+
* // entries: [
|
|
725
|
+
* // { name: 'transformMarkdown', kind: 'function', ... },
|
|
726
|
+
* // { name: 'loadNapiBindings', kind: 'function', ... },
|
|
727
|
+
* // ]
|
|
728
|
+
* // },
|
|
729
|
+
* // ...
|
|
730
|
+
* // ]
|
|
731
|
+
* ```
|
|
732
|
+
*/
|
|
733
|
+
declare function extractDocs(srcDirs: string[], options: ResolvedDocsOptions): Promise<ExtractedDocs[]>;
|
|
734
|
+
/**
|
|
735
|
+
* Generates Markdown documentation from extracted docs.
|
|
736
|
+
*/
|
|
737
|
+
declare function generateMarkdown(docs: ExtractedDocs[], options: ResolvedDocsOptions): Record<string, string>;
|
|
738
|
+
/**
|
|
739
|
+
* Writes generated documentation to the output directory.
|
|
740
|
+
*/
|
|
741
|
+
declare function writeDocs(docs: Record<string, string>, outDir: string, extractedDocs?: ExtractedDocs[], options?: ResolvedDocsOptions): Promise<void>;
|
|
742
|
+
declare function resolveDocsOptions(options: DocsOptions | false | undefined): ResolvedDocsOptions | false;
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* SSG (Static Site Generation) module for ox-content
|
|
746
|
+
*/
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Default HTML template for SSG pages with navigation.
|
|
750
|
+
*/
|
|
751
|
+
declare const DEFAULT_HTML_TEMPLATE = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>{{title}}{{#siteName}} - {{siteName}}{{/siteName}}</title>\n {{#description}}<meta name=\"description\" content=\"{{description}}\">{{/description}}\n <!-- Open Graph -->\n <meta property=\"og:type\" content=\"website\">\n <meta property=\"og:title\" content=\"{{title}}{{#siteName}} - {{siteName}}{{/siteName}}\">\n {{#description}}<meta property=\"og:description\" content=\"{{description}}\">{{/description}}\n {{#ogImage}}<meta property=\"og:image\" content=\"{{ogImage}}\">{{/ogImage}}\n <!-- Twitter Card -->\n <meta name=\"twitter:card\" content=\"summary_large_image\">\n <meta name=\"twitter:title\" content=\"{{title}}{{#siteName}} - {{siteName}}{{/siteName}}\">\n {{#description}}<meta name=\"twitter:description\" content=\"{{description}}\">{{/description}}\n {{#ogImage}}<meta name=\"twitter:image\" content=\"{{ogImage}}\">{{/ogImage}}\n <style>\n :root {\n --sidebar-width: 260px;\n --header-height: 60px;\n --max-content-width: 960px;\n --font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;\n --color-bg: #ffffff;\n --color-bg-alt: #f8f9fa;\n --color-text: #1a1a1a;\n --color-text-muted: #666666;\n --color-border: #e5e7eb;\n --color-primary: #b7410e;\n --color-primary-hover: #ce5937;\n --color-code-bg: #1e293b;\n --color-code-text: #e2e8f0;\n }\n [data-theme=\"dark\"] {\n --color-bg: #0f172a;\n --color-bg-alt: #1e293b;\n --color-text: #e2e8f0;\n --color-text-muted: #94a3b8;\n --color-border: #334155;\n --color-primary: #e67e4d;\n --color-primary-hover: #f4a07a;\n --color-code-bg: #0f172a;\n --color-code-text: #e2e8f0;\n }\n @media (prefers-color-scheme: dark) {\n :root:not([data-theme=\"light\"]) {\n --color-bg: #0f172a;\n --color-bg-alt: #1e293b;\n --color-text: #e2e8f0;\n --color-text-muted: #94a3b8;\n --color-border: #334155;\n --color-primary: #e67e4d;\n --color-primary-hover: #f4a07a;\n --color-code-bg: #0f172a;\n --color-code-text: #e2e8f0;\n }\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n html { scroll-behavior: smooth; }\n body {\n font-family: var(--font-sans);\n line-height: 1.7;\n color: var(--color-text);\n background: var(--color-bg);\n }\n a { color: var(--color-primary); text-decoration: none; }\n a:hover { color: var(--color-primary-hover); text-decoration: underline; }\n\n /* Header */\n .header {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: var(--header-height);\n background: var(--color-bg);\n border-bottom: 1px solid var(--color-border);\n display: flex;\n align-items: center;\n padding: 0 1.5rem;\n z-index: 100;\n }\n .header-title {\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--color-text);\n }\n .header-title:hover { text-decoration: none; }\n .menu-toggle {\n display: none;\n background: none;\n border: none;\n cursor: pointer;\n padding: 0.5rem;\n margin-right: 0.75rem;\n }\n .menu-toggle svg { display: block; }\n .menu-toggle path { stroke: var(--color-text); }\n .header-actions { margin-left: auto; display: flex; align-items: center; gap: 0.5rem; }\n .search-button {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.5rem 0.75rem;\n background: var(--color-bg-alt);\n border: 1px solid var(--color-border);\n border-radius: 6px;\n color: var(--color-text-muted);\n cursor: pointer;\n font-size: 0.875rem;\n transition: border-color 0.15s, color 0.15s;\n }\n .search-button:hover { border-color: var(--color-primary); color: var(--color-text); }\n .search-button svg { width: 16px; height: 16px; }\n .search-button kbd {\n padding: 0.125rem 0.375rem;\n background: var(--color-bg);\n border: 1px solid var(--color-border);\n border-radius: 4px;\n font-family: var(--font-mono);\n font-size: 0.75rem;\n }\n @media (max-width: 640px) {\n .search-button span, .search-button kbd { display: none; }\n .search-button { padding: 0.5rem; }\n }\n .search-modal-overlay {\n display: none;\n position: fixed;\n inset: 0;\n z-index: 200;\n background: rgba(0,0,0,0.6);\n backdrop-filter: blur(4px);\n justify-content: center;\n padding-top: 10vh;\n }\n .search-modal-overlay.open { display: flex; }\n .search-modal {\n width: 100%;\n max-width: 560px;\n margin: 0 1rem;\n background: var(--color-bg);\n border: 1px solid var(--color-border);\n border-radius: 12px;\n overflow: hidden;\n box-shadow: 0 25px 50px -12px rgba(0,0,0,0.4);\n max-height: 70vh;\n display: flex;\n flex-direction: column;\n }\n .search-header {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 1rem;\n border-bottom: 1px solid var(--color-border);\n }\n .search-header svg { flex-shrink: 0; color: var(--color-text-muted); }\n .search-input {\n flex: 1;\n background: none;\n border: none;\n outline: none;\n font-size: 1rem;\n color: var(--color-text);\n }\n .search-input::placeholder { color: var(--color-text-muted); }\n .search-close {\n padding: 0.25rem 0.5rem;\n background: var(--color-bg-alt);\n border: 1px solid var(--color-border);\n border-radius: 4px;\n color: var(--color-text-muted);\n font-family: var(--font-mono);\n font-size: 0.75rem;\n cursor: pointer;\n }\n .search-results {\n flex: 1;\n overflow-y: auto;\n padding: 0.5rem;\n }\n .search-result {\n display: block;\n padding: 0.75rem 1rem;\n border-radius: 8px;\n color: var(--color-text);\n text-decoration: none;\n }\n .search-result:hover, .search-result.selected { background: var(--color-bg-alt); text-decoration: none; }\n .search-result-title { font-weight: 600; font-size: 0.875rem; margin-bottom: 0.25rem; }\n .search-result-snippet { font-size: 0.8125rem; color: var(--color-text-muted); }\n .search-empty { padding: 2rem 1rem; text-align: center; color: var(--color-text-muted); }\n .search-footer {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 1rem;\n padding: 0.75rem 1rem;\n border-top: 1px solid var(--color-border);\n background: var(--color-bg-alt);\n font-size: 0.75rem;\n color: var(--color-text-muted);\n }\n .search-footer kbd {\n padding: 0.125rem 0.375rem;\n background: var(--color-bg);\n border: 1px solid var(--color-border);\n border-radius: 4px;\n font-family: var(--font-mono);\n }\n .theme-toggle {\n background: none;\n border: none;\n cursor: pointer;\n padding: 0.5rem;\n border-radius: 6px;\n color: var(--color-text-muted);\n transition: background 0.15s, color 0.15s;\n }\n .theme-toggle:hover { background: var(--color-bg-alt); color: var(--color-text); }\n .theme-toggle svg { display: block; width: 20px; height: 20px; }\n .theme-toggle .icon-sun { display: none; }\n .theme-toggle .icon-moon { display: block; }\n [data-theme=\"dark\"] .theme-toggle .icon-sun { display: block; }\n [data-theme=\"dark\"] .theme-toggle .icon-moon { display: none; }\n @media (prefers-color-scheme: dark) {\n :root:not([data-theme=\"light\"]) .theme-toggle .icon-sun { display: block; }\n :root:not([data-theme=\"light\"]) .theme-toggle .icon-moon { display: none; }\n }\n\n /* Layout */\n .layout {\n display: flex;\n padding-top: var(--header-height);\n min-height: 100vh;\n }\n\n /* Sidebar */\n .sidebar {\n position: fixed;\n top: var(--header-height);\n left: 0;\n bottom: 0;\n width: var(--sidebar-width);\n background: var(--color-bg-alt);\n border-right: 1px solid var(--color-border);\n overflow-y: auto;\n padding: 1.5rem 1rem;\n }\n .nav-section { margin-bottom: 1.5rem; }\n .nav-title {\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--color-text-muted);\n margin-bottom: 0.5rem;\n padding: 0 0.75rem;\n }\n .nav-list { list-style: none; }\n .nav-item { margin: 0.125rem 0; }\n .nav-link {\n display: block;\n padding: 0.5rem 0.75rem;\n border-radius: 6px;\n color: var(--color-text);\n font-size: 0.875rem;\n transition: background 0.15s;\n }\n .nav-link:hover {\n background: var(--color-border);\n text-decoration: none;\n }\n .nav-link.active {\n background: var(--color-primary);\n color: white;\n }\n\n /* Main content */\n .main {\n flex: 1;\n margin-left: var(--sidebar-width);\n padding: 2rem;\n min-width: 0;\n overflow-x: hidden;\n }\n .content {\n max-width: var(--max-content-width);\n margin: 0 auto;\n overflow-wrap: break-word;\n word-wrap: break-word;\n word-break: break-word;\n }\n\n /* TOC (right sidebar) */\n .toc {\n position: fixed;\n top: calc(var(--header-height) + 2rem);\n right: 2rem;\n width: 200px;\n font-size: 0.8125rem;\n }\n .toc-title {\n font-weight: 600;\n margin-bottom: 0.75rem;\n color: var(--color-text-muted);\n }\n .toc-list { list-style: none; }\n .toc-item { margin: 0.375rem 0; }\n .toc-link {\n color: var(--color-text-muted);\n display: block;\n padding-left: calc((var(--depth, 1) - 1) * 0.75rem);\n }\n .toc-link:hover { color: var(--color-primary); }\n @media (max-width: 1200px) { .toc { display: none; } }\n\n /* Typography */\n .content h1 { font-size: 2.25rem; margin-bottom: 1rem; line-height: 1.2; }\n .content h2 { font-size: 1.5rem; margin-top: 2.5rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--color-border); }\n .content h3 { font-size: 1.25rem; margin-top: 2rem; margin-bottom: 0.75rem; }\n .content h4 { font-size: 1rem; margin-top: 1.5rem; margin-bottom: 0.5rem; }\n .content p { margin-bottom: 1rem; }\n .content ul, .content ol { margin: 1rem 0; padding-left: 1.5rem; }\n .content li { margin: 0.375rem 0; }\n .content blockquote {\n border-left: 4px solid var(--color-primary);\n padding: 0.5rem 1rem;\n margin: 1rem 0;\n background: var(--color-bg-alt);\n border-radius: 0 6px 6px 0;\n }\n .content code {\n font-family: var(--font-mono);\n font-size: 0.875em;\n background: var(--color-bg-alt);\n padding: 0.2em 0.4em;\n border-radius: 4px;\n word-break: break-all;\n }\n .content pre {\n background: var(--color-code-bg);\n color: var(--color-code-text);\n padding: 1rem 1.25rem;\n border-radius: 8px;\n overflow-x: auto;\n margin: 1.5rem 0;\n line-height: 1.5;\n }\n .content pre code {\n background: transparent;\n padding: 0;\n font-size: 0.8125rem;\n }\n .content table {\n width: 100%;\n border-collapse: collapse;\n margin: 1.5rem 0;\n font-size: 0.875rem;\n }\n .content th, .content td {\n border: 1px solid var(--color-border);\n padding: 0.75rem 1rem;\n text-align: left;\n }\n .content th { background: var(--color-bg-alt); font-weight: 600; }\n .content img { max-width: 100%; height: auto; border-radius: 8px; display: block; }\n .content img[alt*=\"Logo\"] { max-width: 200px; display: block; margin: 1rem 0; }\n .content img[alt*=\"Architecture\"] { max-width: 600px; }\n .content img[alt*=\"Benchmark\"] { max-width: 680px; }\n .content hr { border: none; border-top: 1px solid var(--color-border); margin: 2rem 0; }\n\n /* Responsive */\n @media (max-width: 768px) {\n .menu-toggle { display: block; }\n .sidebar {\n transform: translateX(-100%);\n transition: transform 0.3s ease;\n z-index: 99;\n width: 280px;\n }\n .sidebar.open { transform: translateX(0); }\n .main { margin-left: 0; padding: 1rem 0.75rem; }\n .content { padding: 0 0.25rem; }\n .content h1 { font-size: 1.5rem; line-height: 1.3; margin-bottom: 0.75rem; }\n .content h2 { font-size: 1.2rem; margin-top: 2rem; }\n .content h3 { font-size: 1.1rem; }\n .content p { font-size: 0.9375rem; margin-bottom: 0.875rem; }\n .content ul, .content ol { padding-left: 1.25rem; font-size: 0.9375rem; }\n .content pre {\n padding: 0.75rem;\n font-size: 0.75rem;\n margin: 1rem -0.75rem;\n border-radius: 0;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .content code { font-size: 0.8125em; }\n .content table {\n display: block;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n font-size: 0.8125rem;\n margin: 1rem -0.75rem;\n width: calc(100% + 1.5rem);\n }\n .content th, .content td { padding: 0.5rem 0.75rem; white-space: nowrap; }\n .content img { margin: 1rem 0; }\n .content img[alt*=\"Logo\"] { max-width: 150px; }\n .content img[alt*=\"Architecture\"] { max-width: 100%; }\n .content img[alt*=\"Benchmark\"] { max-width: 100%; }\n .content blockquote { padding: 0.5rem 0.75rem; margin: 1rem 0; font-size: 0.9375rem; }\n .header { padding: 0 1rem; }\n .header-title { font-size: 1rem; }\n .header-title img { width: 24px; height: 24px; }\n .overlay {\n display: none;\n position: fixed;\n inset: 0;\n background: rgba(0,0,0,0.5);\n z-index: 98;\n }\n .overlay.open { display: block; }\n }\n\n /* Extra small devices */\n @media (max-width: 480px) {\n .main { padding: 0.75rem 0.5rem; }\n .content h1 { font-size: 1.35rem; }\n .content pre { font-size: 0.6875rem; padding: 0.625rem; }\n .content table { font-size: 0.75rem; }\n .content th, .content td { padding: 0.375rem 0.5rem; }\n }\n </style>\n</head>\n<body>\n <header class=\"header\">\n <button class=\"menu-toggle\" aria-label=\"Toggle menu\">\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke-width=\"2\" stroke-linecap=\"round\">\n <path d=\"M3 12h18M3 6h18M3 18h18\"/>\n </svg>\n </button>\n <a href=\"{{base}}index.html\" class=\"header-title\">\n <img src=\"{{base}}logo.svg\" alt=\"\" width=\"28\" height=\"28\" style=\"margin-right: 8px; vertical-align: middle;\" />\n {{siteName}}\n </a>\n <div class=\"header-actions\">\n <button class=\"search-button\" aria-label=\"Search\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/>\n </svg>\n <span>Search</span>\n <kbd>/</kbd>\n </button>\n <button class=\"theme-toggle\" aria-label=\"Toggle theme\">\n <svg class=\"icon-sun\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"5\"/><path d=\"M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42\"/>\n </svg>\n <svg class=\"icon-moon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"/>\n </svg>\n </button>\n </div>\n </header>\n <div class=\"search-modal-overlay\">\n <div class=\"search-modal\">\n <div class=\"search-header\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/>\n </svg>\n <input type=\"text\" class=\"search-input\" placeholder=\"Search documentation...\" />\n <button class=\"search-close\">Esc</button>\n </div>\n <div class=\"search-results\"></div>\n <div class=\"search-footer\">\n <span><kbd>\u2191</kbd><kbd>\u2193</kbd> to navigate</span>\n <span><kbd>Enter</kbd> to select</span>\n <span><kbd>Esc</kbd> to close</span>\n </div>\n </div>\n </div>\n <div class=\"overlay\"></div>\n <div class=\"layout\">\n <aside class=\"sidebar\">\n <nav>\n{{navigation}}\n </nav>\n </aside>\n <main class=\"main\">\n <article class=\"content\">\n{{content}}\n </article>\n </main>\n{{#hasToc}}\n <aside class=\"toc\">\n <div class=\"toc-title\">On this page</div>\n <ul class=\"toc-list\">\n{{toc}}\n </ul>\n </aside>\n{{/hasToc}}\n </div>\n <script>\n // Menu toggle\n const toggle = document.querySelector('.menu-toggle');\n const sidebar = document.querySelector('.sidebar');\n const overlay = document.querySelector('.overlay');\n if (toggle && sidebar && overlay) {\n const close = () => { sidebar.classList.remove('open'); overlay.classList.remove('open'); };\n toggle.addEventListener('click', () => {\n sidebar.classList.toggle('open');\n overlay.classList.toggle('open');\n });\n overlay.addEventListener('click', close);\n sidebar.querySelectorAll('a').forEach(a => a.addEventListener('click', close));\n }\n\n // Theme toggle\n const themeToggle = document.querySelector('.theme-toggle');\n const getPreferredTheme = () => {\n const stored = localStorage.getItem('theme');\n if (stored) return stored;\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n };\n const setTheme = (theme) => {\n document.documentElement.setAttribute('data-theme', theme);\n localStorage.setItem('theme', theme);\n };\n // Initialize theme\n setTheme(getPreferredTheme());\n if (themeToggle) {\n themeToggle.addEventListener('click', () => {\n const current = document.documentElement.getAttribute('data-theme') || getPreferredTheme();\n setTheme(current === 'dark' ? 'light' : 'dark');\n });\n }\n\n // Search functionality\n const searchButton = document.querySelector('.search-button');\n const searchOverlay = document.querySelector('.search-modal-overlay');\n const searchInput = document.querySelector('.search-input');\n const searchResults = document.querySelector('.search-results');\n const searchClose = document.querySelector('.search-close');\n let searchIndex = null;\n let selectedIndex = 0;\n let results = [];\n\n const openSearch = () => {\n searchOverlay.classList.add('open');\n searchInput.focus();\n };\n const closeSearch = () => {\n searchOverlay.classList.remove('open');\n searchInput.value = '';\n searchResults.innerHTML = '';\n selectedIndex = 0;\n results = [];\n };\n\n // Load search index\n const loadSearchIndex = async () => {\n if (searchIndex) return;\n try {\n const res = await fetch('{{base}}search-index.json');\n searchIndex = await res.json();\n } catch (e) {\n console.warn('Failed to load search index:', e);\n }\n };\n\n // Tokenize query\n const tokenize = (text) => {\n const tokens = [];\n let current = '';\n for (const char of text) {\n const isCjk = /[\\u4E00-\\u9FFF\\u3400-\\u4DBF\\u3040-\\u309F\\u30A0-\\u30FF\\uAC00-\\uD7AF]/.test(char);\n if (isCjk) {\n if (current) { tokens.push(current.toLowerCase()); current = ''; }\n tokens.push(char);\n } else if (/[a-zA-Z0-9_]/.test(char)) {\n current += char;\n } else if (current) {\n tokens.push(current.toLowerCase());\n current = '';\n }\n }\n if (current) tokens.push(current.toLowerCase());\n return tokens;\n };\n\n // Perform search\n const performSearch = async (query) => {\n if (!query.trim()) {\n searchResults.innerHTML = '';\n results = [];\n return;\n }\n await loadSearchIndex();\n if (!searchIndex) {\n searchResults.innerHTML = '<div class=\"search-empty\">Search index not available</div>';\n return;\n }\n\n const tokens = tokenize(query);\n if (!tokens.length) {\n searchResults.innerHTML = '';\n results = [];\n return;\n }\n\n const k1 = 1.2, b = 0.75;\n const docScores = new Map();\n\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n const isLast = i === tokens.length - 1;\n let matchingTerms = [];\n if (isLast && token.length >= 2) {\n matchingTerms = Object.keys(searchIndex.index).filter(t => t.startsWith(token));\n } else if (searchIndex.index[token]) {\n matchingTerms = [token];\n }\n\n for (const term of matchingTerms) {\n const postings = searchIndex.index[term] || [];\n const df = searchIndex.df[term] || 1;\n const idf = Math.log((searchIndex.doc_count - df + 0.5) / (df + 0.5) + 1.0);\n\n for (const posting of postings) {\n const doc = searchIndex.documents[posting.doc_idx];\n if (!doc) continue;\n const boost = posting.field === 'Title' ? 10 : posting.field === 'Heading' ? 5 : 1;\n const tf = posting.tf;\n const docLen = doc.body.length;\n const score = idf * ((tf * (k1 + 1)) / (tf + k1 * (1 - b + b * docLen / searchIndex.avg_dl))) * boost;\n\n if (!docScores.has(posting.doc_idx)) {\n docScores.set(posting.doc_idx, { score: 0, matches: new Set() });\n }\n const entry = docScores.get(posting.doc_idx);\n entry.score += score;\n entry.matches.add(term);\n }\n }\n }\n\n results = Array.from(docScores.entries())\n .map(([docIdx, data]) => {\n const doc = searchIndex.documents[docIdx];\n let snippet = '';\n if (doc.body) {\n const bodyLower = doc.body.toLowerCase();\n let firstPos = -1;\n for (const match of data.matches) {\n const pos = bodyLower.indexOf(match);\n if (pos !== -1 && (firstPos === -1 || pos < firstPos)) firstPos = pos;\n }\n const start = Math.max(0, firstPos - 50);\n const end = Math.min(doc.body.length, start + 150);\n snippet = doc.body.slice(start, end);\n if (start > 0) snippet = '...' + snippet;\n if (end < doc.body.length) snippet += '...';\n }\n return { ...doc, score: data.score, snippet };\n })\n .sort((a, b) => b.score - a.score)\n .slice(0, 10);\n\n selectedIndex = 0;\n renderResults();\n };\n\n const renderResults = () => {\n if (!results.length) {\n searchResults.innerHTML = '<div class=\"search-empty\">No results found</div>';\n return;\n }\n searchResults.innerHTML = results.map((r, i) =>\n '<a href=\"' + r.url + '\" class=\"search-result' + (i === selectedIndex ? ' selected' : '') + '\">' +\n '<div class=\"search-result-title\">' + r.title + '</div>' +\n (r.snippet ? '<div class=\"search-result-snippet\">' + r.snippet + '</div>' : '') +\n '</a>'\n ).join('');\n };\n\n // Event listeners\n if (searchButton) searchButton.addEventListener('click', openSearch);\n if (searchClose) searchClose.addEventListener('click', closeSearch);\n if (searchOverlay) searchOverlay.addEventListener('click', (e) => { if (e.target === searchOverlay) closeSearch(); });\n\n let searchTimeout = null;\n if (searchInput) {\n searchInput.addEventListener('input', () => {\n if (searchTimeout) clearTimeout(searchTimeout);\n searchTimeout = setTimeout(() => performSearch(searchInput.value), 150);\n });\n searchInput.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') closeSearch();\n else if (e.key === 'ArrowDown') {\n e.preventDefault();\n if (selectedIndex < results.length - 1) { selectedIndex++; renderResults(); }\n } else if (e.key === 'ArrowUp') {\n e.preventDefault();\n if (selectedIndex > 0) { selectedIndex--; renderResults(); }\n } else if (e.key === 'Enter' && results[selectedIndex]) {\n e.preventDefault();\n window.location.href = results[selectedIndex].url;\n }\n });\n }\n\n // Global keyboard shortcut (/ or Cmd+K)\n document.addEventListener('keydown', (e) => {\n if ((e.key === '/' && !(e.target instanceof HTMLInputElement)) ||\n ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k')) {\n e.preventDefault();\n openSearch();\n }\n });\n </script>\n</body>\n</html>";
|
|
752
|
+
/**
|
|
753
|
+
* Resolves SSG options with defaults.
|
|
754
|
+
*/
|
|
755
|
+
declare function resolveSsgOptions(ssg: SsgOptions | boolean | undefined): ResolvedSsgOptions;
|
|
756
|
+
/**
|
|
757
|
+
* Builds all markdown files to static HTML.
|
|
758
|
+
*/
|
|
759
|
+
declare function buildSsg(options: ResolvedOptions, root: string): Promise<{
|
|
760
|
+
files: string[];
|
|
761
|
+
errors: string[];
|
|
762
|
+
}>;
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Full-text search functionality for Ox Content.
|
|
766
|
+
*
|
|
767
|
+
* Generates search index at build time and provides client-side search.
|
|
768
|
+
*/
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Resolves search options with defaults.
|
|
772
|
+
*/
|
|
773
|
+
declare function resolveSearchOptions(options: SearchOptions | boolean | undefined): ResolvedSearchOptions;
|
|
774
|
+
/**
|
|
775
|
+
* Builds the search index from Markdown files.
|
|
776
|
+
*/
|
|
777
|
+
declare function buildSearchIndex(srcDir: string, base: string): Promise<string>;
|
|
778
|
+
/**
|
|
779
|
+
* Writes the search index to a file.
|
|
780
|
+
*/
|
|
781
|
+
declare function writeSearchIndex(indexJson: string, outDir: string): Promise<void>;
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Vite Plugin for Ox Content
|
|
785
|
+
*
|
|
786
|
+
* Uses Vite's Environment API for SSG-focused Markdown processing.
|
|
787
|
+
* Provides separate environments for client and server rendering.
|
|
788
|
+
*/
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Creates the Ox Content Vite plugin.
|
|
792
|
+
*
|
|
793
|
+
* @example
|
|
794
|
+
* ```ts
|
|
795
|
+
* // vite.config.ts
|
|
796
|
+
* import { defineConfig } from 'vite';
|
|
797
|
+
* import { oxContent } from 'vite-plugin-ox-content';
|
|
798
|
+
*
|
|
799
|
+
* export default defineConfig({
|
|
800
|
+
* plugins: [
|
|
801
|
+
* oxContent({
|
|
802
|
+
* srcDir: 'docs',
|
|
803
|
+
* gfm: true,
|
|
804
|
+
* }),
|
|
805
|
+
* ],
|
|
806
|
+
* });
|
|
807
|
+
* ```
|
|
808
|
+
*/
|
|
809
|
+
declare function oxContent(options?: OxContentOptions): Plugin[];
|
|
810
|
+
|
|
811
|
+
export { DEFAULT_HTML_TEMPLATE, type DocEntry, type DocsOptions, type ExtractedDocs, type MarkdownNode, type MarkdownTransformer, type NavItem, type OgImageOptions, type OxContentOptions, type ParamDoc, type ResolvedDocsOptions, type ResolvedOptions, type ResolvedSearchOptions, type ResolvedSsgOptions, type ReturnDoc, type SearchDocument, type SearchOptions, type SearchResult, type SsgOptions, type TocEntry, type TransformContext, type TransformResult, buildSearchIndex, buildSsg, createMarkdownEnvironment, extractDocs, generateMarkdown, oxContent, resolveDocsOptions, resolveSearchOptions, resolveSsgOptions, transformMarkdown, writeDocs, writeSearchIndex };
|