mkdnsite 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/LICENSE +21 -0
- package/README.md +211 -0
- package/package.json +72 -0
- package/src/adapters/cloudflare.ts +85 -0
- package/src/adapters/fly.ts +22 -0
- package/src/adapters/local.ts +153 -0
- package/src/adapters/netlify.ts +17 -0
- package/src/adapters/types.ts +54 -0
- package/src/adapters/vercel.ts +48 -0
- package/src/cli.ts +140 -0
- package/src/client/scripts.ts +106 -0
- package/src/config/defaults.ts +68 -0
- package/src/config/schema.ts +192 -0
- package/src/content/filesystem.ts +210 -0
- package/src/content/frontmatter.ts +66 -0
- package/src/content/github.ts +211 -0
- package/src/content/types.ts +86 -0
- package/src/discovery/llmstxt.ts +70 -0
- package/src/handler.ts +188 -0
- package/src/index.ts +57 -0
- package/src/negotiate/accept.ts +72 -0
- package/src/negotiate/headers.ts +56 -0
- package/src/render/bun-native.ts +54 -0
- package/src/render/components/index.ts +149 -0
- package/src/render/page-shell.ts +121 -0
- package/src/render/portable.ts +222 -0
- package/src/render/types.ts +74 -0
- package/src/theme/prose-css.ts +377 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import { access } from 'node:fs/promises'
|
|
4
|
+
import { resolveConfig } from './config/defaults.ts'
|
|
5
|
+
import type { MkdnSiteConfig } from './config/schema.ts'
|
|
6
|
+
import { LocalAdapter } from './adapters/local.ts'
|
|
7
|
+
import { createHandler } from './handler.ts'
|
|
8
|
+
|
|
9
|
+
function parseArgs (args: string[]): Partial<MkdnSiteConfig> {
|
|
10
|
+
const result: Record<string, unknown> = {}
|
|
11
|
+
|
|
12
|
+
for (let i = 0; i < args.length; i++) {
|
|
13
|
+
const arg = args[i]
|
|
14
|
+
|
|
15
|
+
if (arg === '--port' || arg === '-p') {
|
|
16
|
+
result.server = { ...(result.server as object ?? {}), port: parseInt(args[++i], 10) }
|
|
17
|
+
} else if (arg === '--title') {
|
|
18
|
+
result.site = { ...(result.site as object ?? {}), title: args[++i] }
|
|
19
|
+
} else if (arg === '--url') {
|
|
20
|
+
result.site = { ...(result.site as object ?? {}), url: args[++i] }
|
|
21
|
+
} else if (arg === '--no-nav') {
|
|
22
|
+
result.theme = { ...(result.theme as object ?? {}), showNav: false }
|
|
23
|
+
} else if (arg === '--no-llms-txt') {
|
|
24
|
+
result.llmsTxt = { enabled: false }
|
|
25
|
+
} else if (arg === '--no-negotiate') {
|
|
26
|
+
result.negotiation = { enabled: false }
|
|
27
|
+
} else if (arg === '--no-client-js') {
|
|
28
|
+
result.client = { enabled: false, mermaid: false, copyButton: false, themeToggle: false, math: false, search: false }
|
|
29
|
+
} else if (arg === '--no-theme-toggle') {
|
|
30
|
+
result.client = { ...(result.client as object ?? {}), themeToggle: false }
|
|
31
|
+
} else if (arg === '--no-math') {
|
|
32
|
+
result.client = { ...(result.client as object ?? {}), math: false }
|
|
33
|
+
} else if (arg === '--color-scheme') {
|
|
34
|
+
result.theme = { ...(result.theme as object ?? {}), colorScheme: args[++i] }
|
|
35
|
+
} else if (arg === '--theme-mode') {
|
|
36
|
+
result.theme = { ...(result.theme as object ?? {}), mode: args[++i] }
|
|
37
|
+
} else if (arg === '--renderer') {
|
|
38
|
+
result.renderer = args[++i]
|
|
39
|
+
} else if (arg === '--static') {
|
|
40
|
+
result.staticDir = resolve(args[++i])
|
|
41
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
42
|
+
printHelp()
|
|
43
|
+
process.exit(0)
|
|
44
|
+
} else if (arg === '--version' || arg === '-v') {
|
|
45
|
+
console.log('mkdnsite 0.0.1')
|
|
46
|
+
process.exit(0)
|
|
47
|
+
} else if (!arg.startsWith('-')) {
|
|
48
|
+
result.contentDir = resolve(arg)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return result as Partial<MkdnSiteConfig>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function printHelp (): void {
|
|
56
|
+
console.log(`
|
|
57
|
+
mkdnsite — Markdown for the web
|
|
58
|
+
|
|
59
|
+
Usage:
|
|
60
|
+
mkdnsite [directory] [options]
|
|
61
|
+
|
|
62
|
+
Arguments:
|
|
63
|
+
directory Path to markdown content (default: ./content)
|
|
64
|
+
|
|
65
|
+
Options:
|
|
66
|
+
-p, --port <n> Port to listen on (default: 3000)
|
|
67
|
+
--title <text> Site title
|
|
68
|
+
--url <url> Base URL for absolute links
|
|
69
|
+
--static <dir> Directory for static assets
|
|
70
|
+
--color-scheme <val> Color scheme: system (default), light, or dark
|
|
71
|
+
--theme-mode <mode> Theme mode: prose (default) or components
|
|
72
|
+
--no-nav Disable navigation sidebar
|
|
73
|
+
--no-llms-txt Disable /llms.txt generation
|
|
74
|
+
--no-negotiate Disable content negotiation
|
|
75
|
+
--renderer <engine> Renderer: portable (default) or bun-native (Bun only)
|
|
76
|
+
--no-client-js Disable client-side JavaScript (mermaid, copy, search, theme toggle)
|
|
77
|
+
--no-theme-toggle Disable light/dark theme toggle button
|
|
78
|
+
--no-math Disable KaTeX math rendering
|
|
79
|
+
-h, --help Show this help
|
|
80
|
+
-v, --version Show version
|
|
81
|
+
|
|
82
|
+
Content Negotiation:
|
|
83
|
+
Browsers get HTML: curl http://localhost:3000
|
|
84
|
+
AI agents get MD: curl -H "Accept: text/markdown" http://localhost:3000
|
|
85
|
+
Append .md to URL: curl http://localhost:3000/page.md
|
|
86
|
+
AI content index: curl http://localhost:3000/llms.txt
|
|
87
|
+
|
|
88
|
+
https://mkdn.site
|
|
89
|
+
`)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function main (): Promise<void> {
|
|
93
|
+
const args = process.argv.slice(2)
|
|
94
|
+
const cliConfig = parseArgs(args)
|
|
95
|
+
|
|
96
|
+
// Try to load mkdnsite.config.ts from cwd
|
|
97
|
+
let fileConfig: Partial<MkdnSiteConfig> = {}
|
|
98
|
+
try {
|
|
99
|
+
const configPath = resolve('mkdnsite.config.ts')
|
|
100
|
+
await access(configPath)
|
|
101
|
+
const mod = await import(configPath)
|
|
102
|
+
fileConfig = mod.default ?? mod
|
|
103
|
+
} catch {
|
|
104
|
+
// No config file, that's fine
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const merged: Partial<MkdnSiteConfig> = {
|
|
108
|
+
...fileConfig,
|
|
109
|
+
...cliConfig
|
|
110
|
+
}
|
|
111
|
+
if (fileConfig.site != null || cliConfig.site != null) {
|
|
112
|
+
const site: Partial<MkdnSiteConfig['site']> = { ...fileConfig.site, ...cliConfig.site }
|
|
113
|
+
merged.site = site as MkdnSiteConfig['site']
|
|
114
|
+
}
|
|
115
|
+
if (fileConfig.server != null || cliConfig.server != null) {
|
|
116
|
+
const server: Partial<MkdnSiteConfig['server']> = { ...fileConfig.server, ...cliConfig.server }
|
|
117
|
+
merged.server = server as MkdnSiteConfig['server']
|
|
118
|
+
}
|
|
119
|
+
if (fileConfig.theme != null || cliConfig.theme != null) {
|
|
120
|
+
const theme: Partial<MkdnSiteConfig['theme']> = { ...fileConfig.theme, ...cliConfig.theme }
|
|
121
|
+
merged.theme = theme as MkdnSiteConfig['theme']
|
|
122
|
+
}
|
|
123
|
+
if (fileConfig.client != null || cliConfig.client != null) {
|
|
124
|
+
const client: Partial<MkdnSiteConfig['client']> = { ...fileConfig.client, ...cliConfig.client }
|
|
125
|
+
merged.client = client as MkdnSiteConfig['client']
|
|
126
|
+
}
|
|
127
|
+
const config = resolveConfig(merged)
|
|
128
|
+
|
|
129
|
+
const adapter = new LocalAdapter()
|
|
130
|
+
const source = adapter.createContentSource(config)
|
|
131
|
+
const renderer = await adapter.createRenderer(config)
|
|
132
|
+
const handler = createHandler({ source, renderer, config })
|
|
133
|
+
|
|
134
|
+
await adapter.start(handler, config)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
main().catch(err => {
|
|
138
|
+
console.error('Error starting mkdnsite:', err)
|
|
139
|
+
process.exit(1)
|
|
140
|
+
})
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { ClientConfig } from '../config/schema.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate client-side JavaScript for progressive enhancements.
|
|
5
|
+
* Returns a <script> tag string or empty string if all features are disabled.
|
|
6
|
+
*/
|
|
7
|
+
export function CLIENT_SCRIPTS (client: ClientConfig): string {
|
|
8
|
+
if (!client.enabled) return ''
|
|
9
|
+
|
|
10
|
+
const scripts: string[] = []
|
|
11
|
+
|
|
12
|
+
if (client.themeToggle) {
|
|
13
|
+
scripts.push(THEME_TOGGLE_SCRIPT)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (client.copyButton) {
|
|
17
|
+
scripts.push(COPY_BUTTON_SCRIPT)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (client.mermaid) {
|
|
21
|
+
scripts.push(MERMAID_SCRIPT)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (client.search) {
|
|
25
|
+
scripts.push(SEARCH_SCRIPT)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (scripts.length === 0) return ''
|
|
29
|
+
|
|
30
|
+
return `<script>${scripts.join('\n')}</script>`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const THEME_TOGGLE_SCRIPT = `
|
|
34
|
+
(function(){
|
|
35
|
+
var btn = document.querySelector('.mkdn-theme-toggle');
|
|
36
|
+
if (!btn) return;
|
|
37
|
+
btn.addEventListener('click', function(e){
|
|
38
|
+
var isDark = document.documentElement.getAttribute('data-theme') === 'dark';
|
|
39
|
+
var next = isDark ? 'light' : 'dark';
|
|
40
|
+
var rect = btn.getBoundingClientRect();
|
|
41
|
+
var x = rect.left + rect.width / 2;
|
|
42
|
+
var y = rect.top + rect.height / 2;
|
|
43
|
+
document.documentElement.style.setProperty('--mkdn-toggle-x', x + 'px');
|
|
44
|
+
document.documentElement.style.setProperty('--mkdn-toggle-y', y + 'px');
|
|
45
|
+
function apply(){
|
|
46
|
+
document.documentElement.setAttribute('data-theme', next);
|
|
47
|
+
localStorage.setItem('mkdn-theme', next);
|
|
48
|
+
}
|
|
49
|
+
if (document.startViewTransition) {
|
|
50
|
+
document.startViewTransition(apply);
|
|
51
|
+
} else {
|
|
52
|
+
apply();
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
})();
|
|
56
|
+
`.trim()
|
|
57
|
+
|
|
58
|
+
const COPY_BUTTON_SCRIPT = `
|
|
59
|
+
(function(){
|
|
60
|
+
document.querySelectorAll('.mkdn-code-block, .mkdn-prose pre').forEach(function(block){
|
|
61
|
+
var code = block.querySelector('code');
|
|
62
|
+
if(!code) return;
|
|
63
|
+
if(code.classList.contains('language-mermaid')) return;
|
|
64
|
+
var btn = document.createElement('button');
|
|
65
|
+
btn.className = 'mkdn-copy-btn';
|
|
66
|
+
btn.textContent = 'Copy';
|
|
67
|
+
btn.addEventListener('click', function(){
|
|
68
|
+
navigator.clipboard.writeText(code.textContent||'').then(function(){
|
|
69
|
+
btn.textContent = 'Copied!';
|
|
70
|
+
setTimeout(function(){ btn.textContent = 'Copy'; }, 2000);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
block.style.position = 'relative';
|
|
74
|
+
block.appendChild(btn);
|
|
75
|
+
});
|
|
76
|
+
})();
|
|
77
|
+
`.trim()
|
|
78
|
+
|
|
79
|
+
const MERMAID_SCRIPT = `
|
|
80
|
+
(function(){
|
|
81
|
+
var mermaidBlocks = document.querySelectorAll('code.language-mermaid');
|
|
82
|
+
if(mermaidBlocks.length === 0) return;
|
|
83
|
+
var s = document.createElement('script');
|
|
84
|
+
s.src = 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js';
|
|
85
|
+
s.onload = function(){
|
|
86
|
+
mermaid.initialize({ startOnLoad: false, theme: 'default' });
|
|
87
|
+
mermaidBlocks.forEach(function(block){
|
|
88
|
+
var pre = block.parentElement;
|
|
89
|
+
var container = document.createElement('div');
|
|
90
|
+
container.className = 'mkdn-mermaid';
|
|
91
|
+
var id = 'mermaid-' + Math.random().toString(36).substr(2, 9);
|
|
92
|
+
mermaid.render(id, block.textContent||'').then(function(result){
|
|
93
|
+
container.innerHTML = result.svg;
|
|
94
|
+
pre.parentElement.replaceChild(container, pre);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
document.head.appendChild(s);
|
|
99
|
+
})();
|
|
100
|
+
`.trim()
|
|
101
|
+
|
|
102
|
+
const SEARCH_SCRIPT = `
|
|
103
|
+
/* Search: placeholder for client-side search functionality.
|
|
104
|
+
Will be implemented with a pre-built content index served at /search-index.json.
|
|
105
|
+
For MVP, this is a no-op that can be activated once the index endpoint exists. */
|
|
106
|
+
`.trim()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { MkdnSiteConfig } from './schema.ts'
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_CONFIG: MkdnSiteConfig = {
|
|
4
|
+
contentDir: './content',
|
|
5
|
+
site: {
|
|
6
|
+
title: 'mkdnsite',
|
|
7
|
+
lang: 'en'
|
|
8
|
+
},
|
|
9
|
+
server: {
|
|
10
|
+
port: 3000,
|
|
11
|
+
hostname: '0.0.0.0'
|
|
12
|
+
},
|
|
13
|
+
theme: {
|
|
14
|
+
mode: 'prose',
|
|
15
|
+
showNav: true,
|
|
16
|
+
showToc: true,
|
|
17
|
+
colorScheme: 'system',
|
|
18
|
+
syntaxTheme: 'github-light',
|
|
19
|
+
syntaxThemeDark: 'github-dark'
|
|
20
|
+
},
|
|
21
|
+
negotiation: {
|
|
22
|
+
enabled: true,
|
|
23
|
+
includeTokenCount: true,
|
|
24
|
+
contentSignals: {
|
|
25
|
+
aiTrain: 'yes',
|
|
26
|
+
search: 'yes',
|
|
27
|
+
aiInput: 'yes'
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
llmsTxt: {
|
|
31
|
+
enabled: true
|
|
32
|
+
},
|
|
33
|
+
client: {
|
|
34
|
+
enabled: true,
|
|
35
|
+
mermaid: true,
|
|
36
|
+
copyButton: true,
|
|
37
|
+
themeToggle: true,
|
|
38
|
+
math: true,
|
|
39
|
+
search: true
|
|
40
|
+
},
|
|
41
|
+
renderer: 'portable'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Deep merge user config with defaults.
|
|
46
|
+
* User values take precedence at every nesting level.
|
|
47
|
+
*/
|
|
48
|
+
export function resolveConfig (
|
|
49
|
+
userConfig: Partial<MkdnSiteConfig>
|
|
50
|
+
): MkdnSiteConfig {
|
|
51
|
+
return {
|
|
52
|
+
...DEFAULT_CONFIG,
|
|
53
|
+
...userConfig,
|
|
54
|
+
site: { ...DEFAULT_CONFIG.site, ...userConfig.site },
|
|
55
|
+
server: { ...DEFAULT_CONFIG.server, ...userConfig.server },
|
|
56
|
+
theme: { ...DEFAULT_CONFIG.theme, ...userConfig.theme },
|
|
57
|
+
negotiation: {
|
|
58
|
+
...DEFAULT_CONFIG.negotiation,
|
|
59
|
+
...userConfig.negotiation,
|
|
60
|
+
contentSignals: {
|
|
61
|
+
...DEFAULT_CONFIG.negotiation.contentSignals,
|
|
62
|
+
...userConfig.negotiation?.contentSignals
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
llmsTxt: { ...DEFAULT_CONFIG.llmsTxt, ...userConfig.llmsTxt },
|
|
66
|
+
client: { ...DEFAULT_CONFIG.client, ...userConfig.client }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Top-level mkdnsite configuration.
|
|
5
|
+
*
|
|
6
|
+
* Can be defined in mkdnsite.config.ts at the project root,
|
|
7
|
+
* or passed programmatically when creating a handler.
|
|
8
|
+
*/
|
|
9
|
+
export interface MkdnSiteConfig {
|
|
10
|
+
/** Directory containing .md files (default: ./content) */
|
|
11
|
+
contentDir: string
|
|
12
|
+
|
|
13
|
+
/** Site metadata */
|
|
14
|
+
site: SiteConfig
|
|
15
|
+
|
|
16
|
+
/** Server options (local dev / self-hosted only) */
|
|
17
|
+
server: ServerConfig
|
|
18
|
+
|
|
19
|
+
/** Theme and rendering configuration */
|
|
20
|
+
theme: ThemeConfig
|
|
21
|
+
|
|
22
|
+
/** Content negotiation options */
|
|
23
|
+
negotiation: NegotiationConfig
|
|
24
|
+
|
|
25
|
+
/** Auto-generate /llms.txt */
|
|
26
|
+
llmsTxt: LlmsTxtConfig
|
|
27
|
+
|
|
28
|
+
/** Client-side enhancement modules */
|
|
29
|
+
client: ClientConfig
|
|
30
|
+
|
|
31
|
+
/** Markdown renderer engine (default: 'portable') */
|
|
32
|
+
renderer: RendererEngine
|
|
33
|
+
|
|
34
|
+
/** Static files directory for images, videos, etc. */
|
|
35
|
+
staticDir?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SiteConfig {
|
|
39
|
+
title: string
|
|
40
|
+
description?: string
|
|
41
|
+
url?: string
|
|
42
|
+
lang?: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ServerConfig {
|
|
46
|
+
port: number
|
|
47
|
+
hostname: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ThemeConfig {
|
|
51
|
+
/**
|
|
52
|
+
* Rendering mode for markdown content.
|
|
53
|
+
* - 'prose': Uses @tailwindcss/typography prose classes (default)
|
|
54
|
+
* - 'components': Full custom React component overrides per element
|
|
55
|
+
*/
|
|
56
|
+
mode: 'prose' | 'components'
|
|
57
|
+
|
|
58
|
+
/** Custom React components to override default element rendering */
|
|
59
|
+
components?: ComponentOverrides
|
|
60
|
+
|
|
61
|
+
/** Custom CSS file path or URL to use instead of default theme */
|
|
62
|
+
customCss?: string
|
|
63
|
+
|
|
64
|
+
/** Show navigation sidebar */
|
|
65
|
+
showNav: boolean
|
|
66
|
+
|
|
67
|
+
/** Show table of contents per page */
|
|
68
|
+
showToc: boolean
|
|
69
|
+
|
|
70
|
+
/** Edit URL template (e.g. https://github.com/org/repo/edit/main/{path}) */
|
|
71
|
+
editUrl?: string
|
|
72
|
+
|
|
73
|
+
/** Color scheme: 'system' (default), 'light', or 'dark' */
|
|
74
|
+
colorScheme: 'system' | 'light' | 'dark'
|
|
75
|
+
|
|
76
|
+
/** Syntax highlighting theme for Shiki */
|
|
77
|
+
syntaxTheme: string
|
|
78
|
+
|
|
79
|
+
/** Dark mode syntax highlighting theme */
|
|
80
|
+
syntaxThemeDark?: string
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface NegotiationConfig {
|
|
84
|
+
/** Enable serving raw markdown via Accept: text/markdown (default: true) */
|
|
85
|
+
enabled: boolean
|
|
86
|
+
|
|
87
|
+
/** Include x-markdown-tokens header (default: true) */
|
|
88
|
+
includeTokenCount: boolean
|
|
89
|
+
|
|
90
|
+
/** Content-Signal header values */
|
|
91
|
+
contentSignals: ContentSignals
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface ContentSignals {
|
|
95
|
+
aiTrain: 'yes' | 'no'
|
|
96
|
+
search: 'yes' | 'no'
|
|
97
|
+
aiInput: 'yes' | 'no'
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface LlmsTxtConfig {
|
|
101
|
+
enabled: boolean
|
|
102
|
+
description?: string
|
|
103
|
+
sections?: Record<string, string>
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface ClientConfig {
|
|
107
|
+
/**
|
|
108
|
+
* Enable client-side JavaScript enhancements.
|
|
109
|
+
* When false, only static HTML/CSS is served (performance mode).
|
|
110
|
+
* Default: true
|
|
111
|
+
*/
|
|
112
|
+
enabled: boolean
|
|
113
|
+
|
|
114
|
+
/** Enable Mermaid diagram rendering (default: true when client enabled) */
|
|
115
|
+
mermaid: boolean
|
|
116
|
+
|
|
117
|
+
/** Enable copy-to-clipboard on code blocks (default: true when client enabled) */
|
|
118
|
+
copyButton: boolean
|
|
119
|
+
|
|
120
|
+
/** Enable light/dark theme toggle button (default: true when client enabled) */
|
|
121
|
+
themeToggle: boolean
|
|
122
|
+
|
|
123
|
+
/** Enable KaTeX math rendering (default: true when client enabled) */
|
|
124
|
+
math: boolean
|
|
125
|
+
|
|
126
|
+
/** Enable client-side search (default: true when client enabled) */
|
|
127
|
+
search: boolean
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Markdown renderer engine selection.
|
|
132
|
+
* - 'portable': react-markdown + remark/rehype (works everywhere)
|
|
133
|
+
* - 'bun-native': Bun.markdown.react() (Bun only, faster)
|
|
134
|
+
*/
|
|
135
|
+
export type RendererEngine = 'portable' | 'bun-native'
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* React component overrides for markdown elements.
|
|
139
|
+
* Each key maps to an HTML element produced by the markdown renderer.
|
|
140
|
+
*/
|
|
141
|
+
export interface ComponentOverrides {
|
|
142
|
+
h1?: ComponentType<HeadingProps>
|
|
143
|
+
h2?: ComponentType<HeadingProps>
|
|
144
|
+
h3?: ComponentType<HeadingProps>
|
|
145
|
+
h4?: ComponentType<HeadingProps>
|
|
146
|
+
h5?: ComponentType<HeadingProps>
|
|
147
|
+
h6?: ComponentType<HeadingProps>
|
|
148
|
+
p?: ComponentType<{ children?: ReactNode }>
|
|
149
|
+
a?: ComponentType<LinkProps>
|
|
150
|
+
img?: ComponentType<ImageProps>
|
|
151
|
+
pre?: ComponentType<CodeBlockProps>
|
|
152
|
+
code?: ComponentType<InlineCodeProps>
|
|
153
|
+
blockquote?: ComponentType<{ children?: ReactNode }>
|
|
154
|
+
table?: ComponentType<{ children?: ReactNode }>
|
|
155
|
+
thead?: ComponentType<{ children?: ReactNode }>
|
|
156
|
+
tbody?: ComponentType<{ children?: ReactNode }>
|
|
157
|
+
tr?: ComponentType<{ children?: ReactNode }>
|
|
158
|
+
th?: ComponentType<{ children?: ReactNode, align?: string }>
|
|
159
|
+
td?: ComponentType<{ children?: ReactNode, align?: string }>
|
|
160
|
+
ul?: ComponentType<{ children?: ReactNode }>
|
|
161
|
+
ol?: ComponentType<{ children?: ReactNode, start?: number }>
|
|
162
|
+
li?: ComponentType<{ children?: ReactNode, checked?: boolean }>
|
|
163
|
+
hr?: ComponentType<Record<string, never>>
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface HeadingProps {
|
|
167
|
+
children?: ReactNode
|
|
168
|
+
id?: string
|
|
169
|
+
level: number
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface LinkProps {
|
|
173
|
+
children?: ReactNode
|
|
174
|
+
href?: string
|
|
175
|
+
title?: string
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface ImageProps {
|
|
179
|
+
src?: string
|
|
180
|
+
alt?: string
|
|
181
|
+
title?: string
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface CodeBlockProps {
|
|
185
|
+
children?: ReactNode
|
|
186
|
+
language?: string
|
|
187
|
+
raw?: string
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface InlineCodeProps {
|
|
191
|
+
children?: ReactNode
|
|
192
|
+
}
|