@ox-content/vite-plugin 2.86.0 → 2.87.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 +55 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +59 -11
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +59 -11
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +56 -5
- package/dist/index.mjs.map +1 -1
- package/dist/vitepress.cjs +98 -11
- package/dist/vitepress.cjs.map +1 -1
- package/dist/vitepress.mjs +98 -11
- package/dist/vitepress.mjs.map +1 -1
- package/package.json +2 -2
package/dist/vitepress.cjs
CHANGED
|
@@ -67,8 +67,43 @@ function importNapiModuleSync() {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
//#endregion
|
|
70
|
+
//#region src/theme-tokens.ts
|
|
71
|
+
const TOKEN_PREFIX = "--octc-";
|
|
72
|
+
const TOKEN_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
73
|
+
/**
|
|
74
|
+
* Renders light and dark token records as the three selectors the SSG runtime
|
|
75
|
+
* switches between: an explicit `[data-theme="dark"]` opt-in, the OS
|
|
76
|
+
* `prefers-color-scheme` fallback, and the `:root` base.
|
|
77
|
+
*
|
|
78
|
+
* Emitted after the typed color variables and before the theme's own `css`, so
|
|
79
|
+
* a token can override a typed color and raw `css` can override a token.
|
|
80
|
+
*/
|
|
81
|
+
function tokensToCss(light, dark) {
|
|
82
|
+
const lightBody = declarations(light, " ");
|
|
83
|
+
const darkBody = declarations(dark, " ");
|
|
84
|
+
const blocks = [];
|
|
85
|
+
if (lightBody) blocks.push(`:root {\n${lightBody}\n}`);
|
|
86
|
+
if (darkBody) {
|
|
87
|
+
blocks.push(`[data-theme="dark"] {\n${darkBody}\n}`);
|
|
88
|
+
blocks.push(`@media (prefers-color-scheme: dark) {\n :root:not([data-theme="light"]) {\n${declarations(dark, " ")}\n }\n}`);
|
|
89
|
+
}
|
|
90
|
+
return blocks.join("\n");
|
|
91
|
+
}
|
|
92
|
+
function declarations(tokens, indent) {
|
|
93
|
+
return Object.entries(tokens).filter(([, value]) => value !== void 0 && value !== "").map(([name, value]) => `${indent}${TOKEN_PREFIX}${assertTokenName(name)}: ${value};`).join("\n");
|
|
94
|
+
}
|
|
95
|
+
function assertTokenName(name) {
|
|
96
|
+
if (!TOKEN_NAME_PATTERN.test(name)) throw new Error(`Invalid theme token name: ${JSON.stringify(name)}. Token names are lowercase kebab-case without the "${TOKEN_PREFIX}" prefix (e.g. "surface-glass").`);
|
|
97
|
+
return name;
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
70
100
|
//#region src/theme.ts
|
|
71
101
|
/**
|
|
102
|
+
* Theme API for ox-content SSG
|
|
103
|
+
*
|
|
104
|
+
* Provides VitePress-like theming with default theme + customization.
|
|
105
|
+
*/
|
|
106
|
+
/**
|
|
72
107
|
* Default theme configuration.
|
|
73
108
|
* Based on the current ox-content SSG styles.
|
|
74
109
|
*/
|
|
@@ -122,6 +157,8 @@ const defaultTheme = {
|
|
|
122
157
|
},
|
|
123
158
|
socialLinks: {},
|
|
124
159
|
embed: {},
|
|
160
|
+
tokens: {},
|
|
161
|
+
darkTokens: {},
|
|
125
162
|
css: "",
|
|
126
163
|
js: ""
|
|
127
164
|
};
|
|
@@ -161,28 +198,50 @@ function defineTheme(config) {
|
|
|
161
198
|
* Merges multiple theme configurations.
|
|
162
199
|
* Later themes override earlier ones.
|
|
163
200
|
*
|
|
201
|
+
* Object fields (`colors`, `tokens`, `layout`, …) merge key-by-key, but `css`
|
|
202
|
+
* and `js` **concatenate** in layer order — overwriting them would throw away
|
|
203
|
+
* one half of a `[skin, colorScheme]` stack. Identical fragments are joined
|
|
204
|
+
* once, so a layer reached through both an array and an `extends` chain does
|
|
205
|
+
* not emit its stylesheet twice.
|
|
206
|
+
*
|
|
164
207
|
* @example
|
|
165
208
|
* ```ts
|
|
166
|
-
* const merged = mergeThemes(defaultTheme,
|
|
209
|
+
* const merged = mergeThemes(defaultTheme, pixelSkin, tokyoNight, overrides);
|
|
167
210
|
* ```
|
|
168
211
|
*/
|
|
169
212
|
function mergeThemes(...themes) {
|
|
170
|
-
|
|
213
|
+
const layers = themes.flat();
|
|
214
|
+
if (layers.length === 0) return { ...defaultTheme };
|
|
171
215
|
let result = {};
|
|
172
|
-
for (const theme of
|
|
216
|
+
for (const theme of layers) {
|
|
217
|
+
const { css, js, ...rest } = theme;
|
|
218
|
+
result = deepMerge(result, rest);
|
|
219
|
+
const mergedCss = appendSource(result.css, css);
|
|
220
|
+
if (mergedCss) result.css = mergedCss;
|
|
221
|
+
const mergedJs = appendSource(result.js, js);
|
|
222
|
+
if (mergedJs) result.js = mergedJs;
|
|
223
|
+
}
|
|
173
224
|
return result;
|
|
174
225
|
}
|
|
226
|
+
function appendSource(existing, addition) {
|
|
227
|
+
const next = addition?.trim() ?? "";
|
|
228
|
+
const current = existing ?? "";
|
|
229
|
+
if (!next || current.includes(next)) return current;
|
|
230
|
+
return current ? `${current}\n${next}` : next;
|
|
231
|
+
}
|
|
175
232
|
/**
|
|
176
233
|
* Resolves a theme configuration by merging with its extends chain and defaults.
|
|
234
|
+
*
|
|
235
|
+
* An array composes independent layers left to right, which is how a skin
|
|
236
|
+
* package and a color package are stacked:
|
|
237
|
+
*
|
|
238
|
+
* ```ts
|
|
239
|
+
* resolveTheme([pixelSkin, tokyoNight, { footer: { copyright: "2026" } }]);
|
|
240
|
+
* ```
|
|
177
241
|
*/
|
|
178
242
|
function resolveTheme(config) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
let current = config;
|
|
182
|
-
while (current) {
|
|
183
|
-
chain.unshift(current);
|
|
184
|
-
current = current.extends;
|
|
185
|
-
}
|
|
243
|
+
const chain = (config === void 0 ? [defaultTheme] : Array.isArray(config) ? config : [config]).flatMap(expandExtendsChain);
|
|
244
|
+
if (chain.length === 0) chain.push(defaultTheme);
|
|
186
245
|
if (chain[0] !== defaultTheme && chain[0]?.name !== "default") chain.unshift(defaultTheme);
|
|
187
246
|
const merged = mergeThemes(...chain.map(withDerivedCodeBackgroundTop));
|
|
188
247
|
return {
|
|
@@ -197,10 +256,29 @@ function resolveTheme(config) {
|
|
|
197
256
|
socialLinks: merged.socialLinks ?? defaultTheme.socialLinks,
|
|
198
257
|
sidebar: merged.sidebar ?? [],
|
|
199
258
|
embed: merged.embed ?? {},
|
|
259
|
+
tokens: merged.tokens ?? {},
|
|
260
|
+
darkTokens: merged.darkTokens ?? {},
|
|
200
261
|
css: merged.css ?? "",
|
|
201
262
|
js: merged.js ?? ""
|
|
202
263
|
};
|
|
203
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* Flattens one layer's `extends` chain into base-first order.
|
|
267
|
+
*
|
|
268
|
+
* The `seen` guard keeps a theme that accidentally extends itself (or forms a
|
|
269
|
+
* cycle through two packages) from hanging the build.
|
|
270
|
+
*/
|
|
271
|
+
function expandExtendsChain(config) {
|
|
272
|
+
const chain = [];
|
|
273
|
+
const seen = /* @__PURE__ */ new Set();
|
|
274
|
+
let current = config;
|
|
275
|
+
while (current && !seen.has(current)) {
|
|
276
|
+
seen.add(current);
|
|
277
|
+
chain.unshift(current);
|
|
278
|
+
current = current.extends;
|
|
279
|
+
}
|
|
280
|
+
return chain;
|
|
281
|
+
}
|
|
204
282
|
function withDerivedCodeBackgroundTop(theme) {
|
|
205
283
|
const derive = (colors) => {
|
|
206
284
|
if (colors?.codeBackground !== void 0 && colors.codeBackgroundTop === void 0) return {
|
|
@@ -269,10 +347,19 @@ function themeToNapi(theme) {
|
|
|
269
347
|
} : void 0,
|
|
270
348
|
socialLinks,
|
|
271
349
|
embed: Object.keys(theme.embed).length > 0 ? theme.embed : void 0,
|
|
272
|
-
css: theme
|
|
350
|
+
css: themeCss(theme) || void 0,
|
|
273
351
|
js: theme.js || void 0
|
|
274
352
|
};
|
|
275
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* Token blocks come first so a theme's own `css` stays the final word, and both
|
|
356
|
+
* land after the typed color variables the Rust renderer emits.
|
|
357
|
+
*/
|
|
358
|
+
function themeCss(theme) {
|
|
359
|
+
const tokenCss = tokensToCss(theme.tokens, theme.darkTokens);
|
|
360
|
+
if (!tokenCss) return theme.css;
|
|
361
|
+
return theme.css ? `${tokenCss}\n${theme.css}` : tokenCss;
|
|
362
|
+
}
|
|
276
363
|
function socialLinksToNapi(links) {
|
|
277
364
|
if (Array.isArray(links)) {
|
|
278
365
|
const items = links.map((item) => {
|
package/dist/vitepress.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitepress.cjs","names":["createRequire"],"sources":["../src/napi.ts","../src/theme.ts","../src/vitepress.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\n\ntype NapiModule = typeof import(\"@ox-content/napi\");\nconst requireNapi = createRequire(import.meta.url);\n\nfunction getDefaultExport(value: unknown): object | undefined {\n if (!value || typeof value !== \"object\" || !(\"default\" in value)) {\n return undefined;\n }\n\n const defaultExport = value.default;\n return defaultExport && typeof defaultExport === \"object\" ? defaultExport : undefined;\n}\n\nfunction normalizeNapiModule(mod: NapiModule): NapiModule {\n const defaultExport = getDefaultExport(mod);\n return defaultExport\n ? ({\n ...defaultExport,\n ...mod,\n } as NapiModule)\n : mod;\n}\n\nexport async function importNapiModule(): Promise<NapiModule> {\n return normalizeNapiModule((await import(\"@ox-content/napi\")) as NapiModule);\n}\n\nlet syncNapiModule: NapiModule | null | undefined;\n\nexport function importNapiModuleSync(): NapiModule {\n if (syncNapiModule) {\n return syncNapiModule;\n }\n\n if (syncNapiModule === null) {\n throw new Error(\n \"[ox-content] @ox-content/napi is required. Please ensure the NAPI module is built.\",\n );\n }\n\n try {\n const mod = requireNapi(\"@ox-content/napi\") as NapiModule;\n syncNapiModule = normalizeNapiModule(mod);\n return syncNapiModule;\n } catch {\n syncNapiModule = null;\n throw new Error(\n \"[ox-content] @ox-content/napi is required. Please ensure the NAPI module is built.\",\n );\n }\n}\n","/**\n * Theme API for ox-content SSG\n *\n * Provides VitePress-like theming with default theme + customization.\n */\n\n/**\n * Theme color configuration.\n */\nexport interface ThemeColors {\n /** Primary accent color */\n primary?: string;\n /** Primary color on hover */\n primaryHover?: string;\n /** Background color */\n background?: string;\n /** Alternative background color (sidebar, code blocks) */\n backgroundAlt?: string;\n /** Main text color */\n text?: string;\n /** Muted/secondary text color */\n textMuted?: string;\n /** Border color */\n border?: string;\n /** Code block background color */\n codeBackground?: string;\n /** Code block gradient color at the top; defaults to `codeBackground` when customized */\n codeBackgroundTop?: string;\n /** Code block text color */\n codeText?: string;\n}\n\n/**\n * Theme layout configuration.\n */\nexport interface ThemeLayout {\n /** Sidebar width (CSS value, e.g., \"260px\") */\n sidebarWidth?: string;\n /** Header height (CSS value, e.g., \"60px\") */\n headerHeight?: string;\n /** Maximum content width (CSS value, e.g., \"960px\") */\n maxContentWidth?: string;\n}\n\n/**\n * Theme font configuration.\n */\nexport interface ThemeFonts {\n /** Sans-serif font stack */\n sans?: string;\n /** Monospace font stack */\n mono?: string;\n}\n\n/**\n * Entry page theme configuration.\n */\nexport interface ThemeEntryPage {\n /** Landing page presentation mode */\n mode?: \"default\" | \"subtle\";\n}\n\n/**\n * Theme header configuration.\n */\nexport interface ThemeHeader {\n /** Logo image URL */\n logo?: string;\n /** Light mode logo image URL */\n logoLight?: string;\n /** Dark mode logo image URL */\n logoDark?: string;\n /** Whether to render the site name text next to the logo */\n showSiteNameText?: boolean;\n /** Logo width in pixels */\n logoWidth?: number;\n /** Logo height in pixels */\n logoHeight?: number;\n}\n\n/**\n * Theme footer configuration.\n */\nexport interface ThemeFooter {\n /** Footer message (supports HTML) */\n message?: string;\n /** Copyright text (supports HTML) */\n copyright?: string;\n}\n\n/** Custom social link icon. */\nexport type SocialLinkIcon = string | { svg: string };\n\n/** Custom social link. */\nexport interface SocialLink {\n icon: SocialLinkIcon;\n link: string;\n ariaLabel?: string;\n}\n\n/** Legacy social links configuration. */\nexport interface LegacySocialLinks {\n /** GitHub URL */\n github?: string;\n /** Twitter/X URL */\n twitter?: string;\n /** Discord URL */\n discord?: string;\n}\n\n/** Social links configuration. */\nexport type SocialLinks = LegacySocialLinks | SocialLink[];\n\n/**\n * Embedded HTML content for specific positions in the page layout.\n */\nexport interface ThemeEmbed {\n /** Content to embed into <head> */\n head?: string;\n /** Content before header */\n headerBefore?: string;\n /** Content after header */\n headerAfter?: string;\n /** Content before sidebar navigation */\n sidebarBefore?: string;\n /** Content after sidebar navigation */\n sidebarAfter?: string;\n /** Content before main content */\n contentBefore?: string;\n /** Content after main content */\n contentAfter?: string;\n /** Content before footer */\n footerBefore?: string;\n /** Custom footer content (replaces default footer) */\n footer?: string;\n}\n\nexport interface SidebarItem {\n text?: string;\n link?: string;\n items?: SidebarItem[];\n collapsed?: boolean;\n stickyCollapsed?: boolean;\n}\n\n/**\n * Complete theme configuration.\n */\nexport interface ThemeConfig {\n /** Theme name for identification */\n name?: string;\n /** Base theme to extend */\n extends?: ThemeConfig;\n /** Light mode colors (maps to CSS variables) */\n colors?: ThemeColors;\n /** Dark mode colors (maps to CSS variables) */\n darkColors?: ThemeColors;\n /** Font configuration (maps to CSS variables) */\n fonts?: ThemeFonts;\n /** Entry page configuration */\n entryPage?: ThemeEntryPage;\n /** Layout configuration (maps to CSS variables) */\n layout?: ThemeLayout;\n /** Header configuration */\n header?: ThemeHeader;\n /** Footer configuration */\n footer?: ThemeFooter;\n /** Social links configuration */\n socialLinks?: SocialLinks;\n sidebar?: SidebarItem[];\n /** Embedded HTML content at specific positions */\n embed?: ThemeEmbed;\n /** Additional custom CSS */\n css?: string;\n /** Additional custom JavaScript */\n js?: string;\n}\n\n/**\n * Resolved theme configuration (after merging with defaults).\n */\nexport interface ResolvedThemeConfig {\n name: string;\n colors: ThemeColors;\n darkColors: ThemeColors;\n fonts: ThemeFonts;\n entryPage: ThemeEntryPage;\n layout: ThemeLayout;\n header: ThemeHeader;\n footer: ThemeFooter;\n socialLinks: SocialLinks;\n sidebar: SidebarItem[];\n embed: ThemeEmbed;\n css: string;\n js: string;\n}\n\n/**\n * Default theme configuration.\n * Based on the current ox-content SSG styles.\n */\nexport const defaultTheme: ThemeConfig = {\n name: \"default\",\n colors: {\n primary: \"#4f6fae\",\n primaryHover: \"#425f96\",\n background: \"#ffffff\",\n backgroundAlt: \"#f5f7fb\",\n text: \"#131a30\",\n textMuted: \"#4f607b\",\n border: \"#d2dbea\",\n codeBackground: \"#101a31\",\n codeBackgroundTop: \"#18264a\",\n codeText: \"#edf3ff\",\n },\n darkColors: {\n primary: \"#86a4da\",\n primaryHover: \"#a3bbe8\",\n background: \"#060816\",\n backgroundAlt: \"#0d1528\",\n text: \"#ebf2ff\",\n textMuted: \"#8ea0bf\",\n border: \"#223252\",\n codeBackground: \"#0a1020\",\n codeBackgroundTop: \"#0a1020\",\n codeText: \"#e7f0ff\",\n },\n fonts: {\n sans: '\"IBM Plex Sans\", \"Avenir Next\", \"Segoe UI Variable\", \"Segoe UI\", sans-serif',\n mono: '\"IBM Plex Mono\", \"SFMono-Regular\", Consolas, monospace',\n },\n entryPage: {\n mode: \"default\",\n },\n layout: {\n sidebarWidth: \"260px\",\n headerHeight: \"60px\",\n maxContentWidth: \"960px\",\n },\n header: {\n logo: undefined,\n logoLight: undefined,\n logoDark: undefined,\n showSiteNameText: true,\n logoWidth: 28,\n logoHeight: 28,\n },\n footer: {\n message: undefined,\n copyright: undefined,\n },\n socialLinks: {},\n embed: {},\n css: \"\",\n js: \"\",\n};\n\n/**\n * Deep merge two objects.\n */\nfunction deepMerge<T extends Record<string, unknown>>(target: T, source: Partial<T>): T {\n const result = { ...target };\n\n for (const key of Object.keys(source) as (keyof T)[]) {\n const sourceValue = source[key];\n const targetValue = target[key];\n\n if (\n sourceValue !== undefined &&\n typeof sourceValue === \"object\" &&\n sourceValue !== null &&\n !Array.isArray(sourceValue) &&\n typeof targetValue === \"object\" &&\n targetValue !== null &&\n !Array.isArray(targetValue)\n ) {\n result[key] = deepMerge(\n targetValue as Record<string, unknown>,\n sourceValue as Record<string, unknown>,\n ) as T[keyof T];\n } else if (sourceValue !== undefined) {\n result[key] = sourceValue as T[keyof T];\n }\n }\n\n return result;\n}\n\n/**\n * Defines a theme configuration with type checking.\n *\n * @example\n * ```ts\n * const myTheme = defineTheme({\n * extends: defaultTheme,\n * colors: {\n * primary: '#3498db',\n * },\n * footer: {\n * copyright: '2025 My Company',\n * },\n * });\n * ```\n */\nexport function defineTheme(config: ThemeConfig): ThemeConfig {\n return config;\n}\n\n/**\n * Merges multiple theme configurations.\n * Later themes override earlier ones.\n *\n * @example\n * ```ts\n * const merged = mergeThemes(defaultTheme, customTheme, overrides);\n * ```\n */\nexport function mergeThemes(...themes: ThemeConfig[]): ThemeConfig {\n if (themes.length === 0) {\n return { ...defaultTheme };\n }\n\n let result: ThemeConfig = {};\n\n for (const theme of themes) {\n result = deepMerge(\n result as Record<string, unknown>,\n theme as Record<string, unknown>,\n ) as ThemeConfig;\n }\n\n return result;\n}\n\n/**\n * Resolves a theme configuration by merging with its extends chain and defaults.\n */\nexport function resolveTheme(config?: ThemeConfig): ResolvedThemeConfig {\n if (!config) {\n return resolveTheme(defaultTheme);\n }\n\n // Build the extends chain\n const chain: ThemeConfig[] = [];\n let current: ThemeConfig | undefined = config;\n\n while (current) {\n chain.unshift(current);\n current = current.extends;\n }\n\n // Always start with default theme\n if (chain[0] !== defaultTheme && chain[0]?.name !== \"default\") {\n chain.unshift(defaultTheme);\n }\n\n // Merge all themes in the chain\n const merged = mergeThemes(...chain.map(withDerivedCodeBackgroundTop));\n\n // Return resolved config with all required fields\n return {\n name: merged.name ?? \"custom\",\n colors: merged.colors ?? defaultTheme.colors!,\n darkColors: merged.darkColors ?? defaultTheme.darkColors!,\n fonts: merged.fonts ?? defaultTheme.fonts!,\n entryPage: merged.entryPage ?? defaultTheme.entryPage!,\n layout: merged.layout ?? defaultTheme.layout!,\n header: merged.header ?? defaultTheme.header!,\n footer: merged.footer ?? defaultTheme.footer!,\n socialLinks: merged.socialLinks ?? defaultTheme.socialLinks!,\n sidebar: merged.sidebar ?? [],\n embed: merged.embed ?? {},\n css: merged.css ?? \"\",\n js: merged.js ?? \"\",\n };\n}\n\nfunction withDerivedCodeBackgroundTop(theme: ThemeConfig): ThemeConfig {\n const derive = (colors: ThemeColors | undefined): ThemeColors | undefined => {\n if (colors?.codeBackground !== undefined && colors.codeBackgroundTop === undefined) {\n return { ...colors, codeBackgroundTop: colors.codeBackground };\n }\n return colors;\n };\n\n return {\n ...theme,\n colors: derive(theme.colors),\n darkColors: derive(theme.darkColors),\n };\n}\n\n/**\n * Converts resolved theme to the format expected by Rust NAPI.\n */\nexport function themeToNapi(theme: ResolvedThemeConfig): NapiThemeConfig {\n const socialLinks = socialLinksToNapi(theme.socialLinks);\n\n return {\n colors: theme.colors.primary\n ? {\n primary: theme.colors.primary,\n primaryHover: theme.colors.primaryHover,\n background: theme.colors.background,\n backgroundAlt: theme.colors.backgroundAlt,\n text: theme.colors.text,\n textMuted: theme.colors.textMuted,\n border: theme.colors.border,\n codeBackground: theme.colors.codeBackground,\n codeBackgroundTop: theme.colors.codeBackgroundTop,\n codeText: theme.colors.codeText,\n }\n : undefined,\n darkColors: theme.darkColors.primary\n ? {\n primary: theme.darkColors.primary,\n primaryHover: theme.darkColors.primaryHover,\n background: theme.darkColors.background,\n backgroundAlt: theme.darkColors.backgroundAlt,\n text: theme.darkColors.text,\n textMuted: theme.darkColors.textMuted,\n border: theme.darkColors.border,\n codeBackground: theme.darkColors.codeBackground,\n codeBackgroundTop: theme.darkColors.codeBackgroundTop,\n codeText: theme.darkColors.codeText,\n }\n : undefined,\n fonts: theme.fonts.sans\n ? {\n sans: theme.fonts.sans,\n mono: theme.fonts.mono,\n }\n : undefined,\n entryPage: theme.entryPage.mode\n ? {\n mode: theme.entryPage.mode,\n }\n : undefined,\n layout: theme.layout.sidebarWidth\n ? {\n sidebarWidth: theme.layout.sidebarWidth,\n headerHeight: theme.layout.headerHeight,\n maxContentWidth: theme.layout.maxContentWidth,\n }\n : undefined,\n header:\n theme.header.logo || theme.header.logoLight || theme.header.logoDark\n ? {\n logo: theme.header.logo,\n logoLight: theme.header.logoLight,\n logoDark: theme.header.logoDark,\n showSiteNameText: theme.header.showSiteNameText,\n logoWidth: theme.header.logoWidth,\n logoHeight: theme.header.logoHeight,\n }\n : undefined,\n footer:\n theme.footer.message || theme.footer.copyright\n ? {\n message: theme.footer.message,\n copyright: theme.footer.copyright,\n }\n : undefined,\n socialLinks,\n embed: Object.keys(theme.embed).length > 0 ? theme.embed : undefined,\n css: theme.css || undefined,\n js: theme.js || undefined,\n };\n}\n\nfunction socialLinksToNapi(links: SocialLinks): NapiSocialLinks | undefined {\n if (Array.isArray(links)) {\n const items = links.map((item) => {\n const icon = typeof item.icon === \"string\" ? item.icon : undefined;\n const iconSvg = typeof item.icon === \"object\" ? item.icon.svg : undefined;\n return { icon, iconSvg, link: item.link, ariaLabel: item.ariaLabel };\n });\n return items.length > 0 ? { links: items } : undefined;\n }\n\n return links.github || links.twitter || links.discord\n ? { github: links.github, twitter: links.twitter, discord: links.discord }\n : undefined;\n}\n\n/**\n * NAPI-compatible theme colors type.\n */\nexport interface NapiThemeColors {\n primary?: string;\n primaryHover?: string;\n background?: string;\n backgroundAlt?: string;\n text?: string;\n textMuted?: string;\n border?: string;\n codeBackground?: string;\n codeBackgroundTop?: string;\n codeText?: string;\n}\n\n/**\n * NAPI-compatible theme fonts type.\n */\nexport interface NapiThemeFonts {\n sans?: string;\n mono?: string;\n}\n\n/**\n * NAPI-compatible entry page theme type.\n */\nexport interface NapiThemeEntryPage {\n mode?: \"default\" | \"subtle\";\n}\n\n/**\n * NAPI-compatible theme layout type.\n */\nexport interface NapiThemeLayout {\n sidebarWidth?: string;\n headerHeight?: string;\n maxContentWidth?: string;\n}\n\n/**\n * NAPI-compatible theme header type.\n */\nexport interface NapiThemeHeader {\n logo?: string;\n logoLight?: string;\n logoDark?: string;\n showSiteNameText?: boolean;\n logoWidth?: number;\n logoHeight?: number;\n}\n\n/**\n * NAPI-compatible theme footer type.\n */\nexport interface NapiThemeFooter {\n message?: string;\n copyright?: string;\n}\n\n/**\n * NAPI-compatible social links type.\n */\nexport interface NapiSocialLinks {\n github?: string;\n twitter?: string;\n discord?: string;\n links?: NapiSocialLink[];\n}\n\nexport interface NapiSocialLink {\n icon?: string;\n iconSvg?: string;\n link: string;\n ariaLabel?: string;\n}\n\n/**\n * NAPI-compatible theme embed type.\n */\nexport interface NapiThemeEmbed {\n head?: string;\n headerBefore?: string;\n headerAfter?: string;\n sidebarBefore?: string;\n sidebarAfter?: string;\n contentBefore?: string;\n contentAfter?: string;\n footerBefore?: string;\n footer?: string;\n}\n\n/**\n * NAPI-compatible theme configuration type.\n */\nexport interface NapiThemeConfig {\n colors?: NapiThemeColors;\n darkColors?: NapiThemeColors;\n fonts?: NapiThemeFonts;\n entryPage?: NapiThemeEntryPage;\n layout?: NapiThemeLayout;\n header?: NapiThemeHeader;\n footer?: NapiThemeFooter;\n socialLinks?: NapiSocialLinks;\n embed?: NapiThemeEmbed;\n css?: string;\n js?: string;\n}\n","import { importNapiModuleSync } from \"./napi\";\nimport { defineTheme, mergeThemes, type ThemeConfig } from \"./theme\";\nimport type { OxContentOptions, SsgNavigationGroup, SsgNavigationItem } from \"./types\";\n\nexport interface VitePressLogo {\n light?: string;\n dark?: string;\n src?: string;\n alt?: string;\n}\n\nexport interface VitePressSocialLink {\n icon: string;\n link: string;\n ariaLabel?: string;\n}\n\nexport interface VitePressFooter {\n message?: string;\n copyright?: string;\n}\n\nexport interface VitePressSidebarItem {\n text?: string;\n link?: string;\n items?: VitePressSidebarItem[];\n collapsed?: boolean;\n}\n\nexport type VitePressSidebar = VitePressSidebarItem[] | Record<string, VitePressSidebarItem[]>;\n\nexport interface VitePressNavItem {\n text?: string;\n link?: string;\n items?: VitePressNavItem[];\n activeMatch?: string;\n}\n\nexport interface VitePressThemeConfig {\n siteTitle?: string | false;\n logo?: string | VitePressLogo;\n nav?: VitePressNavItem[];\n sidebar?: VitePressSidebar;\n socialLinks?: VitePressSocialLink[];\n footer?: VitePressFooter;\n search?: {\n placeholder?: string;\n };\n}\n\nexport interface VitePressConfig {\n title?: string;\n description?: string;\n base?: string;\n themeConfig?: VitePressThemeConfig;\n}\n\nexport interface GenerateVitePressMigrationConfigOptions {\n importSource?: string;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isExternalLink(value: string): boolean {\n return /^[a-z][a-z0-9+.-]*:/i.test(value) || value.startsWith(\"//\");\n}\n\nfunction splitLink(value: string): { pathname: string; suffix: string } {\n const match = /^([^?#]*)([?#].*)?$/.exec(value);\n return {\n pathname: match?.[1] ?? value,\n suffix: match?.[2] ?? \"\",\n };\n}\n\nfunction normalizeInternalPath(value: string): string {\n const { pathname } = splitLink(value.trim());\n let normalized = pathname || \"/\";\n\n if (!normalized.startsWith(\"/\")) {\n normalized = `/${normalized}`;\n }\n\n normalized = normalized\n .replace(/\\/index(?:\\.(?:html?|md|markdown))?$/i, \"/\")\n .replace(/\\.(?:html?|md|markdown)$/i, \"\");\n\n if (normalized !== \"/\") {\n normalized = normalized.replace(/\\/+$/, \"\");\n }\n\n return normalized || \"/\";\n}\n\nfunction formatTitle(value: string): string {\n return value\n .replace(/[-_]([a-z])/g, (_, char: string) => ` ${char.toUpperCase()}`)\n .replace(/^[a-z]/, (char) => char.toUpperCase());\n}\n\nfunction titleFromPath(value: string): string {\n const normalized = normalizeInternalPath(value);\n if (normalized === \"/\") {\n return \"Home\";\n }\n\n const segment = normalized.split(\"/\").filter(Boolean).pop() ?? \"Page\";\n return formatTitle(segment);\n}\n\nfunction titleFromSidebarKey(value: string): string {\n const segment = value\n .replace(/^\\/+|\\/+$/g, \"\")\n .split(\"/\")\n .filter(Boolean)\n .pop();\n return formatTitle(segment ?? \"guide\");\n}\n\nfunction toNavigationItem(text: string | undefined, link: string): SsgNavigationItem {\n const title = text?.trim() || titleFromPath(link);\n\n if (isExternalLink(link) || link.startsWith(\"#\")) {\n return { title, href: link };\n }\n\n const { suffix } = splitLink(link);\n const path = normalizeInternalPath(link);\n\n return suffix ? { title, path, href: `${path}${suffix}` } : { title, path };\n}\n\nfunction dedupeNavigationItems(items: SsgNavigationItem[]): SsgNavigationItem[] {\n const seen = new Set<string>();\n const next: SsgNavigationItem[] = [];\n\n for (const item of items) {\n const key = `${item.title}::${item.path ?? \"\"}::${item.href ?? \"\"}`;\n if (seen.has(key)) {\n continue;\n }\n seen.add(key);\n next.push(item);\n }\n\n return next;\n}\n\nfunction dedupeNavigationGroups(groups: SsgNavigationGroup[]): SsgNavigationGroup[] {\n const merged = new Map<string, SsgNavigationItem[]>();\n const orderedTitles: string[] = [];\n\n for (const group of groups) {\n if (group.items.length === 0) {\n continue;\n }\n\n if (!merged.has(group.title)) {\n merged.set(group.title, []);\n orderedTitles.push(group.title);\n }\n\n merged.get(group.title)!.push(...group.items);\n }\n\n return orderedTitles.map((title) => ({\n title,\n items: dedupeNavigationItems(merged.get(title) ?? []),\n }));\n}\n\nfunction collectSidebarLinks(items: VitePressSidebarItem[]): SsgNavigationItem[] {\n const links: SsgNavigationItem[] = [];\n\n for (const item of items) {\n if (item.link) {\n links.push(toNavigationItem(item.text, item.link));\n }\n\n if (item.items?.length) {\n links.push(...collectSidebarLinks(item.items));\n }\n }\n\n return dedupeNavigationItems(links);\n}\n\nfunction sidebarArrayToGroups(\n items: VitePressSidebarItem[],\n fallbackTitle: string,\n): SsgNavigationGroup[] {\n const groups: SsgNavigationGroup[] = [];\n const rootItems: SsgNavigationItem[] = [];\n\n for (const item of items) {\n if (item.link) {\n rootItems.push(toNavigationItem(item.text, item.link));\n }\n\n if (item.items?.length) {\n const children = collectSidebarLinks(item.items);\n if (children.length > 0) {\n groups.push({\n title: item.text?.trim() || fallbackTitle,\n items: children,\n });\n }\n }\n }\n\n if (rootItems.length > 0) {\n groups.unshift({\n title: fallbackTitle,\n items: dedupeNavigationItems(rootItems),\n });\n }\n\n return groups;\n}\n\nfunction collectNavLinks(items: VitePressNavItem[]): SsgNavigationItem[] {\n const links: SsgNavigationItem[] = [];\n\n for (const item of items) {\n if (item.link) {\n links.push(toNavigationItem(item.text, item.link));\n }\n\n if (item.items?.length) {\n links.push(...collectNavLinks(item.items));\n }\n }\n\n return dedupeNavigationItems(links);\n}\n\nfunction resolveLogoSrc(logo: string | VitePressLogo | undefined): string | undefined {\n if (!logo) {\n return undefined;\n }\n\n if (typeof logo === \"string\") {\n return logo;\n }\n\n return logo.light ?? logo.dark ?? logo.src;\n}\n\nfunction normalizeSocialIcon(icon: string): \"github\" | \"twitter\" | \"discord\" | undefined {\n const normalized = icon.trim().toLowerCase();\n\n if (normalized === \"github\") return \"github\";\n if (normalized === \"discord\") return \"discord\";\n if (normalized === \"twitter\" || normalized === \"x\" || normalized === \"x-twitter\") {\n return \"twitter\";\n }\n\n return undefined;\n}\n\nfunction toThemeConfig(themeConfig: VitePressThemeConfig | undefined): ThemeConfig | undefined {\n if (!themeConfig) {\n return undefined;\n }\n\n const logo = resolveLogoSrc(themeConfig.logo);\n const socialLinks = Object.fromEntries(\n (themeConfig.socialLinks ?? [])\n .map((link) => {\n const key = normalizeSocialIcon(link.icon);\n return key ? [key, link.link] : null;\n })\n .filter((entry): entry is [string, string] => entry !== null),\n );\n\n const theme: ThemeConfig = {\n ...(logo\n ? {\n header: {\n logo,\n },\n }\n : {}),\n ...(themeConfig.footer?.message || themeConfig.footer?.copyright\n ? {\n footer: {\n message: themeConfig.footer.message,\n copyright: themeConfig.footer.copyright,\n },\n }\n : {}),\n ...(Object.keys(socialLinks).length > 0\n ? {\n socialLinks,\n }\n : {}),\n };\n\n return logo || Object.keys(socialLinks).length > 0 || themeConfig.footer\n ? defineTheme(theme)\n : undefined;\n}\n\nfunction resolveSiteName(config: VitePressConfig): string | undefined {\n const siteTitle = config.themeConfig?.siteTitle;\n if (typeof siteTitle === \"string\" && siteTitle.trim()) {\n return siteTitle;\n }\n\n return config.title;\n}\n\nfunction mergeOxContentOptions(\n baseOptions: OxContentOptions,\n overrides: OxContentOptions,\n): OxContentOptions {\n const mergedSsg =\n overrides.ssg === false\n ? false\n : {\n ...(typeof baseOptions.ssg === \"object\" ? baseOptions.ssg : {}),\n ...(typeof overrides.ssg === \"object\" ? overrides.ssg : {}),\n theme:\n typeof baseOptions.ssg === \"object\" &&\n typeof overrides.ssg === \"object\" &&\n baseOptions.ssg.theme &&\n overrides.ssg.theme\n ? defineTheme(mergeThemes(baseOptions.ssg.theme, overrides.ssg.theme))\n : typeof overrides.ssg === \"object\" && overrides.ssg.theme\n ? overrides.ssg.theme\n : typeof baseOptions.ssg === \"object\"\n ? baseOptions.ssg.theme\n : undefined,\n };\n\n const mergedSearch =\n overrides.search === false\n ? false\n : typeof overrides.search === \"object\"\n ? {\n ...(typeof baseOptions.search === \"object\" ? baseOptions.search : {}),\n ...overrides.search,\n }\n : baseOptions.search;\n\n return {\n ...baseOptions,\n ...overrides,\n ssg: mergedSsg,\n search: mergedSearch,\n };\n}\n\n/**\n * Converts a VitePress sidebar config into ox-content navigation groups.\n * Nested VitePress items are flattened into the nearest ox-content group.\n */\nexport function convertVitePressSidebar(sidebar: VitePressSidebar): SsgNavigationGroup[] {\n if (Array.isArray(sidebar)) {\n return dedupeNavigationGroups(sidebarArrayToGroups(sidebar, \"Guide\"));\n }\n\n const groups = Object.entries(sidebar).flatMap(([key, items]) =>\n sidebarArrayToGroups(items, titleFromSidebarKey(key)),\n );\n\n return dedupeNavigationGroups(groups);\n}\n\n/**\n * Converts VitePress top navigation into ox-content sidebar groups.\n * This is used as a fallback when no explicit sidebar is defined.\n */\nexport function convertVitePressNav(nav: VitePressNavItem[]): SsgNavigationGroup[] {\n const groups: SsgNavigationGroup[] = [];\n const rootItems: SsgNavigationItem[] = [];\n\n for (const item of nav) {\n if (item.link) {\n rootItems.push(toNavigationItem(item.text, item.link));\n }\n\n if (item.items?.length) {\n const children = collectNavLinks(item.items);\n if (children.length > 0) {\n groups.push({\n title: item.text?.trim() || \"Navigation\",\n items: children,\n });\n }\n }\n }\n\n if (rootItems.length > 0) {\n groups.unshift({\n title: \"Navigation\",\n items: dedupeNavigationItems(rootItems),\n });\n }\n\n return dedupeNavigationGroups(groups);\n}\n\n/**\n * Creates ox-content plugin options from an existing VitePress config.\n */\nexport function fromVitePressConfig(\n config: VitePressConfig,\n overrides: OxContentOptions = {},\n): OxContentOptions {\n const theme = toThemeConfig(config.themeConfig);\n const navigation = config.themeConfig?.sidebar\n ? convertVitePressSidebar(config.themeConfig.sidebar)\n : config.themeConfig?.nav\n ? convertVitePressNav(config.themeConfig.nav)\n : undefined;\n\n const migrated: OxContentOptions = {\n ...(config.base ? { base: config.base } : {}),\n ...(config.themeConfig?.search?.placeholder\n ? {\n search: {\n placeholder: config.themeConfig.search.placeholder,\n },\n }\n : {}),\n ssg: {\n ...(resolveSiteName(config) ? { siteName: resolveSiteName(config) } : {}),\n ...(theme ? { theme } : {}),\n ...(navigation ? { navigation } : {}),\n },\n };\n\n return mergeOxContentOptions(migrated, overrides);\n}\n\n/**\n * Generates a TypeScript module exporting migrated ox-content options.\n *\n * This is used by the migration CLI so users can inspect and edit the resulting\n * object instead of keeping a runtime dependency on their VitePress config.\n */\nexport function generateVitePressMigrationConfig(\n config: VitePressConfig,\n overrides: OxContentOptions = {},\n options: GenerateVitePressMigrationConfigOptions = {},\n): string {\n const importSource = options.importSource ?? \"@ox-content/vite-plugin\";\n const migrated = fromVitePressConfig(config, overrides);\n\n return `import type { OxContentOptions } from ${JSON.stringify(importSource)};\n\nconst config = ${formatTsValue(migrated)} satisfies OxContentOptions;\n\nexport default config;\n`;\n}\n\nfunction formatTsValue(value: unknown, depth = 0): string {\n if (value === undefined) {\n return \"undefined\";\n }\n\n if (value === null || typeof value === \"boolean\" || typeof value === \"number\") {\n return JSON.stringify(value);\n }\n\n if (typeof value === \"string\") {\n return JSON.stringify(value);\n }\n\n if (Array.isArray(value)) {\n if (value.length === 0) {\n return \"[]\";\n }\n\n const indent = \" \".repeat(depth + 1);\n const closingIndent = \" \".repeat(depth);\n return `[\\n${value.map((item) => `${indent}${formatTsValue(item, depth + 1)},`).join(\"\\n\")}\\n${closingIndent}]`;\n }\n\n if (isRecord(value)) {\n const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== undefined);\n if (entries.length === 0) {\n return \"{}\";\n }\n\n const indent = \" \".repeat(depth + 1);\n const closingIndent = \" \".repeat(depth);\n return `{\\n${entries\n .map(\n ([key, entryValue]) =>\n `${indent}${formatObjectKey(key)}: ${formatTsValue(entryValue, depth + 1)},`,\n )\n .join(\"\\n\")}\\n${closingIndent}}`;\n }\n\n return \"undefined\";\n}\n\nfunction formatObjectKey(key: string): string {\n return /^[A-Za-z_$][\\w$]*$/.test(key) ? key : JSON.stringify(key);\n}\n\n/**\n * Normalizes VitePress-specific frontmatter into ox-content's entry-page shape.\n */\nexport function normalizeVitePressFrontmatter(\n frontmatter: Record<string, unknown>,\n): Record<string, unknown> {\n return importNapiModuleSync().normalizeVitePressFrontmatter(frontmatter);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vitepress.cjs","names":["createRequire"],"sources":["../src/napi.ts","../src/theme-tokens.ts","../src/theme.ts","../src/vitepress.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\n\ntype NapiModule = typeof import(\"@ox-content/napi\");\nconst requireNapi = createRequire(import.meta.url);\n\nfunction getDefaultExport(value: unknown): object | undefined {\n if (!value || typeof value !== \"object\" || !(\"default\" in value)) {\n return undefined;\n }\n\n const defaultExport = value.default;\n return defaultExport && typeof defaultExport === \"object\" ? defaultExport : undefined;\n}\n\nfunction normalizeNapiModule(mod: NapiModule): NapiModule {\n const defaultExport = getDefaultExport(mod);\n return defaultExport\n ? ({\n ...defaultExport,\n ...mod,\n } as NapiModule)\n : mod;\n}\n\nexport async function importNapiModule(): Promise<NapiModule> {\n return normalizeNapiModule((await import(\"@ox-content/napi\")) as NapiModule);\n}\n\nlet syncNapiModule: NapiModule | null | undefined;\n\nexport function importNapiModuleSync(): NapiModule {\n if (syncNapiModule) {\n return syncNapiModule;\n }\n\n if (syncNapiModule === null) {\n throw new Error(\n \"[ox-content] @ox-content/napi is required. Please ensure the NAPI module is built.\",\n );\n }\n\n try {\n const mod = requireNapi(\"@ox-content/napi\") as NapiModule;\n syncNapiModule = normalizeNapiModule(mod);\n return syncNapiModule;\n } catch {\n syncNapiModule = null;\n throw new Error(\n \"[ox-content] @ox-content/napi is required. Please ensure the NAPI module is built.\",\n );\n }\n}\n","/**\n * Free-form `--octc-*` custom properties for themes that need more than the\n * typed `colors` / `fonts` / `layout` fields.\n *\n * Keys are written **without** the `--octc-` prefix, so `\"surface-glass\"`\n * becomes `--octc-surface-glass`. This is the seam that keeps the two theme\n * axes independent: a color package can restyle code-block line markers, brand\n * accents, and surface textures purely through tokens, while a skin package\n * lays out geometry against those same tokens without knowing any color.\n */\nexport type ThemeTokens = Record<string, string>;\n\nconst TOKEN_PREFIX = \"--octc-\";\nconst TOKEN_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;\n\n/**\n * Renders light and dark token records as the three selectors the SSG runtime\n * switches between: an explicit `[data-theme=\"dark\"]` opt-in, the OS\n * `prefers-color-scheme` fallback, and the `:root` base.\n *\n * Emitted after the typed color variables and before the theme's own `css`, so\n * a token can override a typed color and raw `css` can override a token.\n */\nexport function tokensToCss(light: ThemeTokens, dark: ThemeTokens): string {\n const lightBody = declarations(light, \" \");\n const darkBody = declarations(dark, \" \");\n const blocks: string[] = [];\n\n if (lightBody) {\n blocks.push(`:root {\\n${lightBody}\\n}`);\n }\n if (darkBody) {\n blocks.push(`[data-theme=\"dark\"] {\\n${darkBody}\\n}`);\n blocks.push(\n `@media (prefers-color-scheme: dark) {\\n :root:not([data-theme=\"light\"]) {\\n${declarations(dark, \" \")}\\n }\\n}`,\n );\n }\n\n return blocks.join(\"\\n\");\n}\n\nfunction declarations(tokens: ThemeTokens, indent: string): string {\n return Object.entries(tokens)\n .filter(([, value]) => value !== undefined && value !== \"\")\n .map(([name, value]) => `${indent}${TOKEN_PREFIX}${assertTokenName(name)}: ${value};`)\n .join(\"\\n\");\n}\n\nfunction assertTokenName(name: string): string {\n // Token names land verbatim inside a declaration block, so a stray `:` or `}`\n // would silently break every rule after it. Fail the build with the offending\n // key instead of shipping a corrupt stylesheet.\n if (!TOKEN_NAME_PATTERN.test(name)) {\n throw new Error(\n `Invalid theme token name: ${JSON.stringify(name)}. ` +\n `Token names are lowercase kebab-case without the \"${TOKEN_PREFIX}\" prefix (e.g. \"surface-glass\").`,\n );\n }\n return name;\n}\n","/**\n * Theme API for ox-content SSG\n *\n * Provides VitePress-like theming with default theme + customization.\n */\n\nimport { tokensToCss, type ThemeTokens } from \"./theme-tokens\";\n\nexport type { ThemeTokens } from \"./theme-tokens\";\n\n/**\n * Theme color configuration.\n */\nexport interface ThemeColors {\n /** Primary accent color */\n primary?: string;\n /** Primary color on hover */\n primaryHover?: string;\n /** Background color */\n background?: string;\n /** Alternative background color (sidebar, code blocks) */\n backgroundAlt?: string;\n /** Main text color */\n text?: string;\n /** Muted/secondary text color */\n textMuted?: string;\n /** Border color */\n border?: string;\n /** Code block background color */\n codeBackground?: string;\n /** Code block gradient color at the top; defaults to `codeBackground` when customized */\n codeBackgroundTop?: string;\n /** Code block text color */\n codeText?: string;\n}\n\n/**\n * Theme layout configuration.\n */\nexport interface ThemeLayout {\n /** Sidebar width (CSS value, e.g., \"260px\") */\n sidebarWidth?: string;\n /** Header height (CSS value, e.g., \"60px\") */\n headerHeight?: string;\n /** Maximum content width (CSS value, e.g., \"960px\") */\n maxContentWidth?: string;\n}\n\n/**\n * Theme font configuration.\n */\nexport interface ThemeFonts {\n /** Sans-serif font stack */\n sans?: string;\n /** Monospace font stack */\n mono?: string;\n}\n\n/**\n * Entry page theme configuration.\n */\nexport interface ThemeEntryPage {\n /** Landing page presentation mode */\n mode?: \"default\" | \"subtle\";\n}\n\n/**\n * Theme header configuration.\n */\nexport interface ThemeHeader {\n /** Logo image URL */\n logo?: string;\n /** Light mode logo image URL */\n logoLight?: string;\n /** Dark mode logo image URL */\n logoDark?: string;\n /** Whether to render the site name text next to the logo */\n showSiteNameText?: boolean;\n /** Logo width in pixels */\n logoWidth?: number;\n /** Logo height in pixels */\n logoHeight?: number;\n}\n\n/**\n * Theme footer configuration.\n */\nexport interface ThemeFooter {\n /** Footer message (supports HTML) */\n message?: string;\n /** Copyright text (supports HTML) */\n copyright?: string;\n}\n\n/** Custom social link icon. */\nexport type SocialLinkIcon = string | { svg: string };\n\n/** Custom social link. */\nexport interface SocialLink {\n icon: SocialLinkIcon;\n link: string;\n ariaLabel?: string;\n}\n\n/** Legacy social links configuration. */\nexport interface LegacySocialLinks {\n /** GitHub URL */\n github?: string;\n /** Twitter/X URL */\n twitter?: string;\n /** Discord URL */\n discord?: string;\n}\n\n/** Social links configuration. */\nexport type SocialLinks = LegacySocialLinks | SocialLink[];\n\n/**\n * Embedded HTML content for specific positions in the page layout.\n */\nexport interface ThemeEmbed {\n /** Content to embed into <head> */\n head?: string;\n /** Content before header */\n headerBefore?: string;\n /** Content after header */\n headerAfter?: string;\n /** Content before sidebar navigation */\n sidebarBefore?: string;\n /** Content after sidebar navigation */\n sidebarAfter?: string;\n /** Content before main content */\n contentBefore?: string;\n /** Content after main content */\n contentAfter?: string;\n /** Content before footer */\n footerBefore?: string;\n /** Custom footer content (replaces default footer) */\n footer?: string;\n}\n\nexport interface SidebarItem {\n text?: string;\n link?: string;\n items?: SidebarItem[];\n collapsed?: boolean;\n stickyCollapsed?: boolean;\n}\n\n/**\n * Complete theme configuration.\n */\nexport interface ThemeConfig {\n /** Theme name for identification */\n name?: string;\n /** Base theme to extend */\n extends?: ThemeConfig;\n /** Light mode colors (maps to CSS variables) */\n colors?: ThemeColors;\n /** Dark mode colors (maps to CSS variables) */\n darkColors?: ThemeColors;\n /** Font configuration (maps to CSS variables) */\n fonts?: ThemeFonts;\n /** Entry page configuration */\n entryPage?: ThemeEntryPage;\n /** Layout configuration (maps to CSS variables) */\n layout?: ThemeLayout;\n /** Header configuration */\n header?: ThemeHeader;\n /** Footer configuration */\n footer?: ThemeFooter;\n /** Social links configuration */\n socialLinks?: SocialLinks;\n sidebar?: SidebarItem[];\n /** Embedded HTML content at specific positions */\n embed?: ThemeEmbed;\n /**\n * Extra `--octc-*` custom properties for light mode, keyed without the\n * prefix. Merged key-by-key across composed layers, so a later layer can\n * restyle one token without redeclaring the rest.\n */\n tokens?: ThemeTokens;\n /** Extra `--octc-*` custom properties for dark mode. */\n darkTokens?: ThemeTokens;\n /**\n * Additional custom CSS. Composed layers **concatenate** this rather than\n * overwrite, so stacking a skin and a color scheme keeps both stylesheets.\n */\n css?: string;\n /** Additional custom JavaScript. Concatenated across composed layers. */\n js?: string;\n}\n\n/**\n * Resolved theme configuration (after merging with defaults).\n */\nexport interface ResolvedThemeConfig {\n name: string;\n colors: ThemeColors;\n darkColors: ThemeColors;\n fonts: ThemeFonts;\n entryPage: ThemeEntryPage;\n layout: ThemeLayout;\n header: ThemeHeader;\n footer: ThemeFooter;\n socialLinks: SocialLinks;\n sidebar: SidebarItem[];\n embed: ThemeEmbed;\n tokens: ThemeTokens;\n darkTokens: ThemeTokens;\n css: string;\n js: string;\n}\n\n/**\n * Default theme configuration.\n * Based on the current ox-content SSG styles.\n */\nexport const defaultTheme: ThemeConfig = {\n name: \"default\",\n colors: {\n primary: \"#4f6fae\",\n primaryHover: \"#425f96\",\n background: \"#ffffff\",\n backgroundAlt: \"#f5f7fb\",\n text: \"#131a30\",\n textMuted: \"#4f607b\",\n border: \"#d2dbea\",\n codeBackground: \"#101a31\",\n codeBackgroundTop: \"#18264a\",\n codeText: \"#edf3ff\",\n },\n darkColors: {\n primary: \"#86a4da\",\n primaryHover: \"#a3bbe8\",\n background: \"#060816\",\n backgroundAlt: \"#0d1528\",\n text: \"#ebf2ff\",\n textMuted: \"#8ea0bf\",\n border: \"#223252\",\n codeBackground: \"#0a1020\",\n codeBackgroundTop: \"#0a1020\",\n codeText: \"#e7f0ff\",\n },\n fonts: {\n sans: '\"IBM Plex Sans\", \"Avenir Next\", \"Segoe UI Variable\", \"Segoe UI\", sans-serif',\n mono: '\"IBM Plex Mono\", \"SFMono-Regular\", Consolas, monospace',\n },\n entryPage: {\n mode: \"default\",\n },\n layout: {\n sidebarWidth: \"260px\",\n headerHeight: \"60px\",\n maxContentWidth: \"960px\",\n },\n header: {\n logo: undefined,\n logoLight: undefined,\n logoDark: undefined,\n showSiteNameText: true,\n logoWidth: 28,\n logoHeight: 28,\n },\n footer: {\n message: undefined,\n copyright: undefined,\n },\n socialLinks: {},\n embed: {},\n tokens: {},\n darkTokens: {},\n css: \"\",\n js: \"\",\n};\n\n/**\n * Deep merge two objects.\n */\nfunction deepMerge<T extends Record<string, unknown>>(target: T, source: Partial<T>): T {\n const result = { ...target };\n\n for (const key of Object.keys(source) as (keyof T)[]) {\n const sourceValue = source[key];\n const targetValue = target[key];\n\n if (\n sourceValue !== undefined &&\n typeof sourceValue === \"object\" &&\n sourceValue !== null &&\n !Array.isArray(sourceValue) &&\n typeof targetValue === \"object\" &&\n targetValue !== null &&\n !Array.isArray(targetValue)\n ) {\n result[key] = deepMerge(\n targetValue as Record<string, unknown>,\n sourceValue as Record<string, unknown>,\n ) as T[keyof T];\n } else if (sourceValue !== undefined) {\n result[key] = sourceValue as T[keyof T];\n }\n }\n\n return result;\n}\n\n/**\n * Defines a theme configuration with type checking.\n *\n * @example\n * ```ts\n * const myTheme = defineTheme({\n * extends: defaultTheme,\n * colors: {\n * primary: '#3498db',\n * },\n * footer: {\n * copyright: '2025 My Company',\n * },\n * });\n * ```\n */\nexport function defineTheme(config: ThemeConfig): ThemeConfig {\n return config;\n}\n\n/**\n * Merges multiple theme configurations.\n * Later themes override earlier ones.\n *\n * Object fields (`colors`, `tokens`, `layout`, …) merge key-by-key, but `css`\n * and `js` **concatenate** in layer order — overwriting them would throw away\n * one half of a `[skin, colorScheme]` stack. Identical fragments are joined\n * once, so a layer reached through both an array and an `extends` chain does\n * not emit its stylesheet twice.\n *\n * @example\n * ```ts\n * const merged = mergeThemes(defaultTheme, pixelSkin, tokyoNight, overrides);\n * ```\n */\nexport function mergeThemes(...themes: (ThemeConfig | ThemeConfig[])[]): ThemeConfig {\n const layers = themes.flat();\n if (layers.length === 0) {\n return { ...defaultTheme };\n }\n\n let result: ThemeConfig = {};\n\n for (const theme of layers) {\n const { css, js, ...rest } = theme;\n result = deepMerge(\n result as Record<string, unknown>,\n rest as Record<string, unknown>,\n ) as ThemeConfig;\n\n const mergedCss = appendSource(result.css, css);\n if (mergedCss) {\n result.css = mergedCss;\n }\n const mergedJs = appendSource(result.js, js);\n if (mergedJs) {\n result.js = mergedJs;\n }\n }\n\n return result;\n}\n\nfunction appendSource(existing: string | undefined, addition: string | undefined): string {\n const next = addition?.trim() ?? \"\";\n const current = existing ?? \"\";\n if (!next || current.includes(next)) {\n return current;\n }\n return current ? `${current}\\n${next}` : next;\n}\n\n/**\n * Resolves a theme configuration by merging with its extends chain and defaults.\n *\n * An array composes independent layers left to right, which is how a skin\n * package and a color package are stacked:\n *\n * ```ts\n * resolveTheme([pixelSkin, tokyoNight, { footer: { copyright: \"2026\" } }]);\n * ```\n */\nexport function resolveTheme(config?: ThemeConfig | ThemeConfig[]): ResolvedThemeConfig {\n const layers = config === undefined ? [defaultTheme] : Array.isArray(config) ? config : [config];\n const chain = layers.flatMap(expandExtendsChain);\n\n // Always start with default theme\n if (chain.length === 0) {\n chain.push(defaultTheme);\n }\n if (chain[0] !== defaultTheme && chain[0]?.name !== \"default\") {\n chain.unshift(defaultTheme);\n }\n\n // Merge all themes in the chain\n const merged = mergeThemes(...chain.map(withDerivedCodeBackgroundTop));\n\n // Return resolved config with all required fields\n return {\n name: merged.name ?? \"custom\",\n colors: merged.colors ?? defaultTheme.colors!,\n darkColors: merged.darkColors ?? defaultTheme.darkColors!,\n fonts: merged.fonts ?? defaultTheme.fonts!,\n entryPage: merged.entryPage ?? defaultTheme.entryPage!,\n layout: merged.layout ?? defaultTheme.layout!,\n header: merged.header ?? defaultTheme.header!,\n footer: merged.footer ?? defaultTheme.footer!,\n socialLinks: merged.socialLinks ?? defaultTheme.socialLinks!,\n sidebar: merged.sidebar ?? [],\n embed: merged.embed ?? {},\n tokens: merged.tokens ?? {},\n darkTokens: merged.darkTokens ?? {},\n css: merged.css ?? \"\",\n js: merged.js ?? \"\",\n };\n}\n\n/**\n * Flattens one layer's `extends` chain into base-first order.\n *\n * The `seen` guard keeps a theme that accidentally extends itself (or forms a\n * cycle through two packages) from hanging the build.\n */\nfunction expandExtendsChain(config: ThemeConfig): ThemeConfig[] {\n const chain: ThemeConfig[] = [];\n const seen = new Set<ThemeConfig>();\n let current: ThemeConfig | undefined = config;\n\n while (current && !seen.has(current)) {\n seen.add(current);\n chain.unshift(current);\n current = current.extends;\n }\n\n return chain;\n}\n\nfunction withDerivedCodeBackgroundTop(theme: ThemeConfig): ThemeConfig {\n const derive = (colors: ThemeColors | undefined): ThemeColors | undefined => {\n if (colors?.codeBackground !== undefined && colors.codeBackgroundTop === undefined) {\n return { ...colors, codeBackgroundTop: colors.codeBackground };\n }\n return colors;\n };\n\n return {\n ...theme,\n colors: derive(theme.colors),\n darkColors: derive(theme.darkColors),\n };\n}\n\n/**\n * Converts resolved theme to the format expected by Rust NAPI.\n */\nexport function themeToNapi(theme: ResolvedThemeConfig): NapiThemeConfig {\n const socialLinks = socialLinksToNapi(theme.socialLinks);\n\n return {\n colors: theme.colors.primary\n ? {\n primary: theme.colors.primary,\n primaryHover: theme.colors.primaryHover,\n background: theme.colors.background,\n backgroundAlt: theme.colors.backgroundAlt,\n text: theme.colors.text,\n textMuted: theme.colors.textMuted,\n border: theme.colors.border,\n codeBackground: theme.colors.codeBackground,\n codeBackgroundTop: theme.colors.codeBackgroundTop,\n codeText: theme.colors.codeText,\n }\n : undefined,\n darkColors: theme.darkColors.primary\n ? {\n primary: theme.darkColors.primary,\n primaryHover: theme.darkColors.primaryHover,\n background: theme.darkColors.background,\n backgroundAlt: theme.darkColors.backgroundAlt,\n text: theme.darkColors.text,\n textMuted: theme.darkColors.textMuted,\n border: theme.darkColors.border,\n codeBackground: theme.darkColors.codeBackground,\n codeBackgroundTop: theme.darkColors.codeBackgroundTop,\n codeText: theme.darkColors.codeText,\n }\n : undefined,\n fonts: theme.fonts.sans\n ? {\n sans: theme.fonts.sans,\n mono: theme.fonts.mono,\n }\n : undefined,\n entryPage: theme.entryPage.mode\n ? {\n mode: theme.entryPage.mode,\n }\n : undefined,\n layout: theme.layout.sidebarWidth\n ? {\n sidebarWidth: theme.layout.sidebarWidth,\n headerHeight: theme.layout.headerHeight,\n maxContentWidth: theme.layout.maxContentWidth,\n }\n : undefined,\n header:\n theme.header.logo || theme.header.logoLight || theme.header.logoDark\n ? {\n logo: theme.header.logo,\n logoLight: theme.header.logoLight,\n logoDark: theme.header.logoDark,\n showSiteNameText: theme.header.showSiteNameText,\n logoWidth: theme.header.logoWidth,\n logoHeight: theme.header.logoHeight,\n }\n : undefined,\n footer:\n theme.footer.message || theme.footer.copyright\n ? {\n message: theme.footer.message,\n copyright: theme.footer.copyright,\n }\n : undefined,\n socialLinks,\n embed: Object.keys(theme.embed).length > 0 ? theme.embed : undefined,\n css: themeCss(theme) || undefined,\n js: theme.js || undefined,\n };\n}\n\n/**\n * Token blocks come first so a theme's own `css` stays the final word, and both\n * land after the typed color variables the Rust renderer emits.\n */\nfunction themeCss(theme: ResolvedThemeConfig): string {\n const tokenCss = tokensToCss(theme.tokens, theme.darkTokens);\n if (!tokenCss) {\n return theme.css;\n }\n return theme.css ? `${tokenCss}\\n${theme.css}` : tokenCss;\n}\n\nfunction socialLinksToNapi(links: SocialLinks): NapiSocialLinks | undefined {\n if (Array.isArray(links)) {\n const items = links.map((item) => {\n const icon = typeof item.icon === \"string\" ? item.icon : undefined;\n const iconSvg = typeof item.icon === \"object\" ? item.icon.svg : undefined;\n return { icon, iconSvg, link: item.link, ariaLabel: item.ariaLabel };\n });\n return items.length > 0 ? { links: items } : undefined;\n }\n\n return links.github || links.twitter || links.discord\n ? { github: links.github, twitter: links.twitter, discord: links.discord }\n : undefined;\n}\n\n/**\n * NAPI-compatible theme colors type.\n */\nexport interface NapiThemeColors {\n primary?: string;\n primaryHover?: string;\n background?: string;\n backgroundAlt?: string;\n text?: string;\n textMuted?: string;\n border?: string;\n codeBackground?: string;\n codeBackgroundTop?: string;\n codeText?: string;\n}\n\n/**\n * NAPI-compatible theme fonts type.\n */\nexport interface NapiThemeFonts {\n sans?: string;\n mono?: string;\n}\n\n/**\n * NAPI-compatible entry page theme type.\n */\nexport interface NapiThemeEntryPage {\n mode?: \"default\" | \"subtle\";\n}\n\n/**\n * NAPI-compatible theme layout type.\n */\nexport interface NapiThemeLayout {\n sidebarWidth?: string;\n headerHeight?: string;\n maxContentWidth?: string;\n}\n\n/**\n * NAPI-compatible theme header type.\n */\nexport interface NapiThemeHeader {\n logo?: string;\n logoLight?: string;\n logoDark?: string;\n showSiteNameText?: boolean;\n logoWidth?: number;\n logoHeight?: number;\n}\n\n/**\n * NAPI-compatible theme footer type.\n */\nexport interface NapiThemeFooter {\n message?: string;\n copyright?: string;\n}\n\n/**\n * NAPI-compatible social links type.\n */\nexport interface NapiSocialLinks {\n github?: string;\n twitter?: string;\n discord?: string;\n links?: NapiSocialLink[];\n}\n\nexport interface NapiSocialLink {\n icon?: string;\n iconSvg?: string;\n link: string;\n ariaLabel?: string;\n}\n\n/**\n * NAPI-compatible theme embed type.\n */\nexport interface NapiThemeEmbed {\n head?: string;\n headerBefore?: string;\n headerAfter?: string;\n sidebarBefore?: string;\n sidebarAfter?: string;\n contentBefore?: string;\n contentAfter?: string;\n footerBefore?: string;\n footer?: string;\n}\n\n/**\n * NAPI-compatible theme configuration type.\n */\nexport interface NapiThemeConfig {\n colors?: NapiThemeColors;\n darkColors?: NapiThemeColors;\n fonts?: NapiThemeFonts;\n entryPage?: NapiThemeEntryPage;\n layout?: NapiThemeLayout;\n header?: NapiThemeHeader;\n footer?: NapiThemeFooter;\n socialLinks?: NapiSocialLinks;\n embed?: NapiThemeEmbed;\n css?: string;\n js?: string;\n}\n","import { importNapiModuleSync } from \"./napi\";\nimport { defineTheme, mergeThemes, type ThemeConfig } from \"./theme\";\nimport type { OxContentOptions, SsgNavigationGroup, SsgNavigationItem } from \"./types\";\n\nexport interface VitePressLogo {\n light?: string;\n dark?: string;\n src?: string;\n alt?: string;\n}\n\nexport interface VitePressSocialLink {\n icon: string;\n link: string;\n ariaLabel?: string;\n}\n\nexport interface VitePressFooter {\n message?: string;\n copyright?: string;\n}\n\nexport interface VitePressSidebarItem {\n text?: string;\n link?: string;\n items?: VitePressSidebarItem[];\n collapsed?: boolean;\n}\n\nexport type VitePressSidebar = VitePressSidebarItem[] | Record<string, VitePressSidebarItem[]>;\n\nexport interface VitePressNavItem {\n text?: string;\n link?: string;\n items?: VitePressNavItem[];\n activeMatch?: string;\n}\n\nexport interface VitePressThemeConfig {\n siteTitle?: string | false;\n logo?: string | VitePressLogo;\n nav?: VitePressNavItem[];\n sidebar?: VitePressSidebar;\n socialLinks?: VitePressSocialLink[];\n footer?: VitePressFooter;\n search?: {\n placeholder?: string;\n };\n}\n\nexport interface VitePressConfig {\n title?: string;\n description?: string;\n base?: string;\n themeConfig?: VitePressThemeConfig;\n}\n\nexport interface GenerateVitePressMigrationConfigOptions {\n importSource?: string;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isExternalLink(value: string): boolean {\n return /^[a-z][a-z0-9+.-]*:/i.test(value) || value.startsWith(\"//\");\n}\n\nfunction splitLink(value: string): { pathname: string; suffix: string } {\n const match = /^([^?#]*)([?#].*)?$/.exec(value);\n return {\n pathname: match?.[1] ?? value,\n suffix: match?.[2] ?? \"\",\n };\n}\n\nfunction normalizeInternalPath(value: string): string {\n const { pathname } = splitLink(value.trim());\n let normalized = pathname || \"/\";\n\n if (!normalized.startsWith(\"/\")) {\n normalized = `/${normalized}`;\n }\n\n normalized = normalized\n .replace(/\\/index(?:\\.(?:html?|md|markdown))?$/i, \"/\")\n .replace(/\\.(?:html?|md|markdown)$/i, \"\");\n\n if (normalized !== \"/\") {\n normalized = normalized.replace(/\\/+$/, \"\");\n }\n\n return normalized || \"/\";\n}\n\nfunction formatTitle(value: string): string {\n return value\n .replace(/[-_]([a-z])/g, (_, char: string) => ` ${char.toUpperCase()}`)\n .replace(/^[a-z]/, (char) => char.toUpperCase());\n}\n\nfunction titleFromPath(value: string): string {\n const normalized = normalizeInternalPath(value);\n if (normalized === \"/\") {\n return \"Home\";\n }\n\n const segment = normalized.split(\"/\").filter(Boolean).pop() ?? \"Page\";\n return formatTitle(segment);\n}\n\nfunction titleFromSidebarKey(value: string): string {\n const segment = value\n .replace(/^\\/+|\\/+$/g, \"\")\n .split(\"/\")\n .filter(Boolean)\n .pop();\n return formatTitle(segment ?? \"guide\");\n}\n\nfunction toNavigationItem(text: string | undefined, link: string): SsgNavigationItem {\n const title = text?.trim() || titleFromPath(link);\n\n if (isExternalLink(link) || link.startsWith(\"#\")) {\n return { title, href: link };\n }\n\n const { suffix } = splitLink(link);\n const path = normalizeInternalPath(link);\n\n return suffix ? { title, path, href: `${path}${suffix}` } : { title, path };\n}\n\nfunction dedupeNavigationItems(items: SsgNavigationItem[]): SsgNavigationItem[] {\n const seen = new Set<string>();\n const next: SsgNavigationItem[] = [];\n\n for (const item of items) {\n const key = `${item.title}::${item.path ?? \"\"}::${item.href ?? \"\"}`;\n if (seen.has(key)) {\n continue;\n }\n seen.add(key);\n next.push(item);\n }\n\n return next;\n}\n\nfunction dedupeNavigationGroups(groups: SsgNavigationGroup[]): SsgNavigationGroup[] {\n const merged = new Map<string, SsgNavigationItem[]>();\n const orderedTitles: string[] = [];\n\n for (const group of groups) {\n if (group.items.length === 0) {\n continue;\n }\n\n if (!merged.has(group.title)) {\n merged.set(group.title, []);\n orderedTitles.push(group.title);\n }\n\n merged.get(group.title)!.push(...group.items);\n }\n\n return orderedTitles.map((title) => ({\n title,\n items: dedupeNavigationItems(merged.get(title) ?? []),\n }));\n}\n\nfunction collectSidebarLinks(items: VitePressSidebarItem[]): SsgNavigationItem[] {\n const links: SsgNavigationItem[] = [];\n\n for (const item of items) {\n if (item.link) {\n links.push(toNavigationItem(item.text, item.link));\n }\n\n if (item.items?.length) {\n links.push(...collectSidebarLinks(item.items));\n }\n }\n\n return dedupeNavigationItems(links);\n}\n\nfunction sidebarArrayToGroups(\n items: VitePressSidebarItem[],\n fallbackTitle: string,\n): SsgNavigationGroup[] {\n const groups: SsgNavigationGroup[] = [];\n const rootItems: SsgNavigationItem[] = [];\n\n for (const item of items) {\n if (item.link) {\n rootItems.push(toNavigationItem(item.text, item.link));\n }\n\n if (item.items?.length) {\n const children = collectSidebarLinks(item.items);\n if (children.length > 0) {\n groups.push({\n title: item.text?.trim() || fallbackTitle,\n items: children,\n });\n }\n }\n }\n\n if (rootItems.length > 0) {\n groups.unshift({\n title: fallbackTitle,\n items: dedupeNavigationItems(rootItems),\n });\n }\n\n return groups;\n}\n\nfunction collectNavLinks(items: VitePressNavItem[]): SsgNavigationItem[] {\n const links: SsgNavigationItem[] = [];\n\n for (const item of items) {\n if (item.link) {\n links.push(toNavigationItem(item.text, item.link));\n }\n\n if (item.items?.length) {\n links.push(...collectNavLinks(item.items));\n }\n }\n\n return dedupeNavigationItems(links);\n}\n\nfunction resolveLogoSrc(logo: string | VitePressLogo | undefined): string | undefined {\n if (!logo) {\n return undefined;\n }\n\n if (typeof logo === \"string\") {\n return logo;\n }\n\n return logo.light ?? logo.dark ?? logo.src;\n}\n\nfunction normalizeSocialIcon(icon: string): \"github\" | \"twitter\" | \"discord\" | undefined {\n const normalized = icon.trim().toLowerCase();\n\n if (normalized === \"github\") return \"github\";\n if (normalized === \"discord\") return \"discord\";\n if (normalized === \"twitter\" || normalized === \"x\" || normalized === \"x-twitter\") {\n return \"twitter\";\n }\n\n return undefined;\n}\n\nfunction toThemeConfig(themeConfig: VitePressThemeConfig | undefined): ThemeConfig | undefined {\n if (!themeConfig) {\n return undefined;\n }\n\n const logo = resolveLogoSrc(themeConfig.logo);\n const socialLinks = Object.fromEntries(\n (themeConfig.socialLinks ?? [])\n .map((link) => {\n const key = normalizeSocialIcon(link.icon);\n return key ? [key, link.link] : null;\n })\n .filter((entry): entry is [string, string] => entry !== null),\n );\n\n const theme: ThemeConfig = {\n ...(logo\n ? {\n header: {\n logo,\n },\n }\n : {}),\n ...(themeConfig.footer?.message || themeConfig.footer?.copyright\n ? {\n footer: {\n message: themeConfig.footer.message,\n copyright: themeConfig.footer.copyright,\n },\n }\n : {}),\n ...(Object.keys(socialLinks).length > 0\n ? {\n socialLinks,\n }\n : {}),\n };\n\n return logo || Object.keys(socialLinks).length > 0 || themeConfig.footer\n ? defineTheme(theme)\n : undefined;\n}\n\nfunction resolveSiteName(config: VitePressConfig): string | undefined {\n const siteTitle = config.themeConfig?.siteTitle;\n if (typeof siteTitle === \"string\" && siteTitle.trim()) {\n return siteTitle;\n }\n\n return config.title;\n}\n\nfunction mergeOxContentOptions(\n baseOptions: OxContentOptions,\n overrides: OxContentOptions,\n): OxContentOptions {\n const mergedSsg =\n overrides.ssg === false\n ? false\n : {\n ...(typeof baseOptions.ssg === \"object\" ? baseOptions.ssg : {}),\n ...(typeof overrides.ssg === \"object\" ? overrides.ssg : {}),\n theme:\n typeof baseOptions.ssg === \"object\" &&\n typeof overrides.ssg === \"object\" &&\n baseOptions.ssg.theme &&\n overrides.ssg.theme\n ? defineTheme(mergeThemes(baseOptions.ssg.theme, overrides.ssg.theme))\n : typeof overrides.ssg === \"object\" && overrides.ssg.theme\n ? overrides.ssg.theme\n : typeof baseOptions.ssg === \"object\"\n ? baseOptions.ssg.theme\n : undefined,\n };\n\n const mergedSearch =\n overrides.search === false\n ? false\n : typeof overrides.search === \"object\"\n ? {\n ...(typeof baseOptions.search === \"object\" ? baseOptions.search : {}),\n ...overrides.search,\n }\n : baseOptions.search;\n\n return {\n ...baseOptions,\n ...overrides,\n ssg: mergedSsg,\n search: mergedSearch,\n };\n}\n\n/**\n * Converts a VitePress sidebar config into ox-content navigation groups.\n * Nested VitePress items are flattened into the nearest ox-content group.\n */\nexport function convertVitePressSidebar(sidebar: VitePressSidebar): SsgNavigationGroup[] {\n if (Array.isArray(sidebar)) {\n return dedupeNavigationGroups(sidebarArrayToGroups(sidebar, \"Guide\"));\n }\n\n const groups = Object.entries(sidebar).flatMap(([key, items]) =>\n sidebarArrayToGroups(items, titleFromSidebarKey(key)),\n );\n\n return dedupeNavigationGroups(groups);\n}\n\n/**\n * Converts VitePress top navigation into ox-content sidebar groups.\n * This is used as a fallback when no explicit sidebar is defined.\n */\nexport function convertVitePressNav(nav: VitePressNavItem[]): SsgNavigationGroup[] {\n const groups: SsgNavigationGroup[] = [];\n const rootItems: SsgNavigationItem[] = [];\n\n for (const item of nav) {\n if (item.link) {\n rootItems.push(toNavigationItem(item.text, item.link));\n }\n\n if (item.items?.length) {\n const children = collectNavLinks(item.items);\n if (children.length > 0) {\n groups.push({\n title: item.text?.trim() || \"Navigation\",\n items: children,\n });\n }\n }\n }\n\n if (rootItems.length > 0) {\n groups.unshift({\n title: \"Navigation\",\n items: dedupeNavigationItems(rootItems),\n });\n }\n\n return dedupeNavigationGroups(groups);\n}\n\n/**\n * Creates ox-content plugin options from an existing VitePress config.\n */\nexport function fromVitePressConfig(\n config: VitePressConfig,\n overrides: OxContentOptions = {},\n): OxContentOptions {\n const theme = toThemeConfig(config.themeConfig);\n const navigation = config.themeConfig?.sidebar\n ? convertVitePressSidebar(config.themeConfig.sidebar)\n : config.themeConfig?.nav\n ? convertVitePressNav(config.themeConfig.nav)\n : undefined;\n\n const migrated: OxContentOptions = {\n ...(config.base ? { base: config.base } : {}),\n ...(config.themeConfig?.search?.placeholder\n ? {\n search: {\n placeholder: config.themeConfig.search.placeholder,\n },\n }\n : {}),\n ssg: {\n ...(resolveSiteName(config) ? { siteName: resolveSiteName(config) } : {}),\n ...(theme ? { theme } : {}),\n ...(navigation ? { navigation } : {}),\n },\n };\n\n return mergeOxContentOptions(migrated, overrides);\n}\n\n/**\n * Generates a TypeScript module exporting migrated ox-content options.\n *\n * This is used by the migration CLI so users can inspect and edit the resulting\n * object instead of keeping a runtime dependency on their VitePress config.\n */\nexport function generateVitePressMigrationConfig(\n config: VitePressConfig,\n overrides: OxContentOptions = {},\n options: GenerateVitePressMigrationConfigOptions = {},\n): string {\n const importSource = options.importSource ?? \"@ox-content/vite-plugin\";\n const migrated = fromVitePressConfig(config, overrides);\n\n return `import type { OxContentOptions } from ${JSON.stringify(importSource)};\n\nconst config = ${formatTsValue(migrated)} satisfies OxContentOptions;\n\nexport default config;\n`;\n}\n\nfunction formatTsValue(value: unknown, depth = 0): string {\n if (value === undefined) {\n return \"undefined\";\n }\n\n if (value === null || typeof value === \"boolean\" || typeof value === \"number\") {\n return JSON.stringify(value);\n }\n\n if (typeof value === \"string\") {\n return JSON.stringify(value);\n }\n\n if (Array.isArray(value)) {\n if (value.length === 0) {\n return \"[]\";\n }\n\n const indent = \" \".repeat(depth + 1);\n const closingIndent = \" \".repeat(depth);\n return `[\\n${value.map((item) => `${indent}${formatTsValue(item, depth + 1)},`).join(\"\\n\")}\\n${closingIndent}]`;\n }\n\n if (isRecord(value)) {\n const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== undefined);\n if (entries.length === 0) {\n return \"{}\";\n }\n\n const indent = \" \".repeat(depth + 1);\n const closingIndent = \" \".repeat(depth);\n return `{\\n${entries\n .map(\n ([key, entryValue]) =>\n `${indent}${formatObjectKey(key)}: ${formatTsValue(entryValue, depth + 1)},`,\n )\n .join(\"\\n\")}\\n${closingIndent}}`;\n }\n\n return \"undefined\";\n}\n\nfunction formatObjectKey(key: string): string {\n return /^[A-Za-z_$][\\w$]*$/.test(key) ? key : JSON.stringify(key);\n}\n\n/**\n * Normalizes VitePress-specific frontmatter into ox-content's entry-page shape.\n */\nexport function normalizeVitePressFrontmatter(\n frontmatter: Record<string, unknown>,\n): Record<string, unknown> {\n return importNapiModuleSync().normalizeVitePressFrontmatter(frontmatter);\n}\n"],"mappings":"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"}
|
package/dist/vitepress.mjs
CHANGED
|
@@ -29,8 +29,43 @@ function importNapiModuleSync() {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
//#endregion
|
|
32
|
+
//#region src/theme-tokens.ts
|
|
33
|
+
const TOKEN_PREFIX = "--octc-";
|
|
34
|
+
const TOKEN_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
35
|
+
/**
|
|
36
|
+
* Renders light and dark token records as the three selectors the SSG runtime
|
|
37
|
+
* switches between: an explicit `[data-theme="dark"]` opt-in, the OS
|
|
38
|
+
* `prefers-color-scheme` fallback, and the `:root` base.
|
|
39
|
+
*
|
|
40
|
+
* Emitted after the typed color variables and before the theme's own `css`, so
|
|
41
|
+
* a token can override a typed color and raw `css` can override a token.
|
|
42
|
+
*/
|
|
43
|
+
function tokensToCss(light, dark) {
|
|
44
|
+
const lightBody = declarations(light, " ");
|
|
45
|
+
const darkBody = declarations(dark, " ");
|
|
46
|
+
const blocks = [];
|
|
47
|
+
if (lightBody) blocks.push(`:root {\n${lightBody}\n}`);
|
|
48
|
+
if (darkBody) {
|
|
49
|
+
blocks.push(`[data-theme="dark"] {\n${darkBody}\n}`);
|
|
50
|
+
blocks.push(`@media (prefers-color-scheme: dark) {\n :root:not([data-theme="light"]) {\n${declarations(dark, " ")}\n }\n}`);
|
|
51
|
+
}
|
|
52
|
+
return blocks.join("\n");
|
|
53
|
+
}
|
|
54
|
+
function declarations(tokens, indent) {
|
|
55
|
+
return Object.entries(tokens).filter(([, value]) => value !== void 0 && value !== "").map(([name, value]) => `${indent}${TOKEN_PREFIX}${assertTokenName(name)}: ${value};`).join("\n");
|
|
56
|
+
}
|
|
57
|
+
function assertTokenName(name) {
|
|
58
|
+
if (!TOKEN_NAME_PATTERN.test(name)) throw new Error(`Invalid theme token name: ${JSON.stringify(name)}. Token names are lowercase kebab-case without the "${TOKEN_PREFIX}" prefix (e.g. "surface-glass").`);
|
|
59
|
+
return name;
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
32
62
|
//#region src/theme.ts
|
|
33
63
|
/**
|
|
64
|
+
* Theme API for ox-content SSG
|
|
65
|
+
*
|
|
66
|
+
* Provides VitePress-like theming with default theme + customization.
|
|
67
|
+
*/
|
|
68
|
+
/**
|
|
34
69
|
* Default theme configuration.
|
|
35
70
|
* Based on the current ox-content SSG styles.
|
|
36
71
|
*/
|
|
@@ -84,6 +119,8 @@ const defaultTheme = {
|
|
|
84
119
|
},
|
|
85
120
|
socialLinks: {},
|
|
86
121
|
embed: {},
|
|
122
|
+
tokens: {},
|
|
123
|
+
darkTokens: {},
|
|
87
124
|
css: "",
|
|
88
125
|
js: ""
|
|
89
126
|
};
|
|
@@ -123,28 +160,50 @@ function defineTheme(config) {
|
|
|
123
160
|
* Merges multiple theme configurations.
|
|
124
161
|
* Later themes override earlier ones.
|
|
125
162
|
*
|
|
163
|
+
* Object fields (`colors`, `tokens`, `layout`, …) merge key-by-key, but `css`
|
|
164
|
+
* and `js` **concatenate** in layer order — overwriting them would throw away
|
|
165
|
+
* one half of a `[skin, colorScheme]` stack. Identical fragments are joined
|
|
166
|
+
* once, so a layer reached through both an array and an `extends` chain does
|
|
167
|
+
* not emit its stylesheet twice.
|
|
168
|
+
*
|
|
126
169
|
* @example
|
|
127
170
|
* ```ts
|
|
128
|
-
* const merged = mergeThemes(defaultTheme,
|
|
171
|
+
* const merged = mergeThemes(defaultTheme, pixelSkin, tokyoNight, overrides);
|
|
129
172
|
* ```
|
|
130
173
|
*/
|
|
131
174
|
function mergeThemes(...themes) {
|
|
132
|
-
|
|
175
|
+
const layers = themes.flat();
|
|
176
|
+
if (layers.length === 0) return { ...defaultTheme };
|
|
133
177
|
let result = {};
|
|
134
|
-
for (const theme of
|
|
178
|
+
for (const theme of layers) {
|
|
179
|
+
const { css, js, ...rest } = theme;
|
|
180
|
+
result = deepMerge(result, rest);
|
|
181
|
+
const mergedCss = appendSource(result.css, css);
|
|
182
|
+
if (mergedCss) result.css = mergedCss;
|
|
183
|
+
const mergedJs = appendSource(result.js, js);
|
|
184
|
+
if (mergedJs) result.js = mergedJs;
|
|
185
|
+
}
|
|
135
186
|
return result;
|
|
136
187
|
}
|
|
188
|
+
function appendSource(existing, addition) {
|
|
189
|
+
const next = addition?.trim() ?? "";
|
|
190
|
+
const current = existing ?? "";
|
|
191
|
+
if (!next || current.includes(next)) return current;
|
|
192
|
+
return current ? `${current}\n${next}` : next;
|
|
193
|
+
}
|
|
137
194
|
/**
|
|
138
195
|
* Resolves a theme configuration by merging with its extends chain and defaults.
|
|
196
|
+
*
|
|
197
|
+
* An array composes independent layers left to right, which is how a skin
|
|
198
|
+
* package and a color package are stacked:
|
|
199
|
+
*
|
|
200
|
+
* ```ts
|
|
201
|
+
* resolveTheme([pixelSkin, tokyoNight, { footer: { copyright: "2026" } }]);
|
|
202
|
+
* ```
|
|
139
203
|
*/
|
|
140
204
|
function resolveTheme(config) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
let current = config;
|
|
144
|
-
while (current) {
|
|
145
|
-
chain.unshift(current);
|
|
146
|
-
current = current.extends;
|
|
147
|
-
}
|
|
205
|
+
const chain = (config === void 0 ? [defaultTheme] : Array.isArray(config) ? config : [config]).flatMap(expandExtendsChain);
|
|
206
|
+
if (chain.length === 0) chain.push(defaultTheme);
|
|
148
207
|
if (chain[0] !== defaultTheme && chain[0]?.name !== "default") chain.unshift(defaultTheme);
|
|
149
208
|
const merged = mergeThemes(...chain.map(withDerivedCodeBackgroundTop));
|
|
150
209
|
return {
|
|
@@ -159,10 +218,29 @@ function resolveTheme(config) {
|
|
|
159
218
|
socialLinks: merged.socialLinks ?? defaultTheme.socialLinks,
|
|
160
219
|
sidebar: merged.sidebar ?? [],
|
|
161
220
|
embed: merged.embed ?? {},
|
|
221
|
+
tokens: merged.tokens ?? {},
|
|
222
|
+
darkTokens: merged.darkTokens ?? {},
|
|
162
223
|
css: merged.css ?? "",
|
|
163
224
|
js: merged.js ?? ""
|
|
164
225
|
};
|
|
165
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Flattens one layer's `extends` chain into base-first order.
|
|
229
|
+
*
|
|
230
|
+
* The `seen` guard keeps a theme that accidentally extends itself (or forms a
|
|
231
|
+
* cycle through two packages) from hanging the build.
|
|
232
|
+
*/
|
|
233
|
+
function expandExtendsChain(config) {
|
|
234
|
+
const chain = [];
|
|
235
|
+
const seen = /* @__PURE__ */ new Set();
|
|
236
|
+
let current = config;
|
|
237
|
+
while (current && !seen.has(current)) {
|
|
238
|
+
seen.add(current);
|
|
239
|
+
chain.unshift(current);
|
|
240
|
+
current = current.extends;
|
|
241
|
+
}
|
|
242
|
+
return chain;
|
|
243
|
+
}
|
|
166
244
|
function withDerivedCodeBackgroundTop(theme) {
|
|
167
245
|
const derive = (colors) => {
|
|
168
246
|
if (colors?.codeBackground !== void 0 && colors.codeBackgroundTop === void 0) return {
|
|
@@ -231,10 +309,19 @@ function themeToNapi(theme) {
|
|
|
231
309
|
} : void 0,
|
|
232
310
|
socialLinks,
|
|
233
311
|
embed: Object.keys(theme.embed).length > 0 ? theme.embed : void 0,
|
|
234
|
-
css: theme
|
|
312
|
+
css: themeCss(theme) || void 0,
|
|
235
313
|
js: theme.js || void 0
|
|
236
314
|
};
|
|
237
315
|
}
|
|
316
|
+
/**
|
|
317
|
+
* Token blocks come first so a theme's own `css` stays the final word, and both
|
|
318
|
+
* land after the typed color variables the Rust renderer emits.
|
|
319
|
+
*/
|
|
320
|
+
function themeCss(theme) {
|
|
321
|
+
const tokenCss = tokensToCss(theme.tokens, theme.darkTokens);
|
|
322
|
+
if (!tokenCss) return theme.css;
|
|
323
|
+
return theme.css ? `${tokenCss}\n${theme.css}` : tokenCss;
|
|
324
|
+
}
|
|
238
325
|
function socialLinksToNapi(links) {
|
|
239
326
|
if (Array.isArray(links)) {
|
|
240
327
|
const items = links.map((item) => {
|