@ox-content/vite-plugin 2.16.0 → 2.25.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/bin/migrate-vitepress.mjs +27 -0
- package/dist/index.cjs +339 -352
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +22 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +339 -352
- package/dist/index.mjs.map +1 -1
- package/dist/mermaid.cjs +19 -48
- package/dist/mermaid.cjs.map +1 -1
- package/dist/mermaid.mjs +2 -109
- package/dist/mermaid2.mjs +92 -2
- package/dist/mermaid2.mjs.map +1 -0
- package/dist/napi.cjs +45 -0
- package/dist/napi.cjs.map +1 -0
- package/dist/napi.mjs +34 -0
- package/dist/napi.mjs.map +1 -0
- package/dist/ogp.cjs +1 -1
- package/dist/ogp.cjs.map +1 -1
- package/dist/ogp2.mjs +1 -1
- package/dist/ogp2.mjs.map +1 -1
- package/dist/vitepress-cli.cjs +1 -1
- package/dist/vitepress-cli.mjs +1 -1
- package/dist/vitepress.cjs +2 -26
- package/dist/vitepress.cjs.map +1 -1
- package/dist/vitepress.mjs +2 -26
- package/dist/vitepress.mjs.map +1 -1
- package/package.json +5 -4
- package/dist/mermaid.mjs.map +0 -1
package/dist/ogp2.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ogp2.mjs","names":[],"sources":["../src/plugins/ogp.ts"],"sourcesContent":["/**\n * OGP Card Plugin - Link card embedding\n *\n * Transforms <OgCard> components into static link preview cards\n * by fetching OGP metadata at build time.\n */\n\nimport { unified } from \"unified\";\nimport rehypeParse from \"rehype-parse\";\nimport rehypeStringify from \"rehype-stringify\";\nimport type { Root, Element } from \"hast\";\n\nexport interface OgpData {\n url: string;\n title: string;\n description?: string;\n image?: string;\n siteName?: string;\n favicon?: string;\n}\n\nexport interface OgpOptions {\n /** Request timeout in milliseconds. Default: 10000 */\n timeout?: number;\n /** Cache fetched data. Default: true */\n cache?: boolean;\n /** Cache TTL in milliseconds. Default: 3600000 (1 hour) */\n cacheTTL?: number;\n /** User agent for requests */\n userAgent?: string;\n}\n\nconst defaultOptions: Required<OgpOptions> = {\n timeout: 10000,\n cache: true,\n cacheTTL: 3600000,\n userAgent: \"ox-content-ogp-bot/1.0 (compatible; +https://github.com/ubugeeei/ox-content)\",\n};\n\n// Simple in-memory cache\nconst ogpCache = new Map<string, { data: OgpData; timestamp: number }>();\n\nfunction isPrivateIPv4(hostname: string): boolean {\n const parts = hostname.split(\".\").map(Number);\n if (\n parts.length !== 4 ||\n parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)\n ) {\n return false;\n }\n const [a, b] = parts;\n return (\n a === 10 ||\n a === 127 ||\n a === 0 ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && b === 168) ||\n (a === 169 && b === 254)\n );\n}\n\nexport function isSafeOgpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n const host = url.hostname.toLowerCase();\n const ipv6 = host.replace(/^\\[|\\]$/g, \"\");\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return false;\n if (host === \"localhost\" || host.endsWith(\".localhost\")) return false;\n if (\n ipv6.includes(\":\") &&\n (ipv6 === \"::1\" || ipv6.startsWith(\"fc\") || ipv6.startsWith(\"fd\") || ipv6.startsWith(\"fe80\"))\n )\n return false;\n return !isPrivateIPv4(host);\n } catch {\n return false;\n }\n}\n\n/**\n * Get element attribute value.\n */\nfunction getAttribute(el: Element, name: string): string | undefined {\n const value = el.properties?.[name];\n if (typeof value === \"string\") return value;\n if (Array.isArray(value)) return value.join(\" \");\n return undefined;\n}\n\n/**\n * Extract domain from URL.\n */\nfunction extractDomain(url: string): string {\n try {\n const urlObj = new URL(url);\n return urlObj.hostname;\n } catch {\n return url;\n }\n}\n\n/**\n * Get favicon URL for a domain.\n */\nfunction getFaviconUrl(url: string): string {\n try {\n const urlObj = new URL(url);\n // Use Google's favicon service as fallback\n return `https://www.google.com/s2/favicons?domain=${urlObj.hostname}&sz=32`;\n } catch {\n return \"\";\n }\n}\n\n/**\n * Parse OGP metadata from HTML.\n */\nfunction parseOgpFromHtml(html: string, url: string): OgpData {\n const result: OgpData = {\n url,\n title: \"\",\n };\n\n // Extract title\n const titleMatch = html.match(/<title[^>]*>([^<]+)<\\/title>/i);\n const ogTitleMatch =\n html.match(/<meta[^>]*property=[\"']og:title[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:title[\"']/i);\n\n result.title = ogTitleMatch?.[1] || titleMatch?.[1] || extractDomain(url);\n\n // Extract description\n const descMatch =\n html.match(/<meta[^>]*property=[\"']og:description[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:description[\"']/i) ||\n html.match(/<meta[^>]*name=[\"']description[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*name=[\"']description[\"']/i);\n\n if (descMatch) {\n result.description = descMatch[1];\n }\n\n // Extract image\n const imageMatch =\n html.match(/<meta[^>]*property=[\"']og:image[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:image[\"']/i);\n\n if (imageMatch) {\n let imageUrl = imageMatch[1];\n // Handle relative URLs\n if (imageUrl.startsWith(\"/\")) {\n try {\n const urlObj = new URL(url);\n imageUrl = `${urlObj.protocol}//${urlObj.host}${imageUrl}`;\n } catch {\n // Keep as is\n }\n }\n result.image = imageUrl;\n }\n\n // Extract site name\n const siteNameMatch =\n html.match(/<meta[^>]*property=[\"']og:site_name[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:site_name[\"']/i);\n\n if (siteNameMatch) {\n result.siteName = siteNameMatch[1];\n }\n\n // Get favicon\n result.favicon = getFaviconUrl(url);\n\n return result;\n}\n\n/**\n * Fetch OGP data for a URL.\n */\nexport async function fetchOgpData(\n url: string,\n options: Required<OgpOptions>,\n): Promise<OgpData | null> {\n if (!isSafeOgpUrl(url)) {\n return null;\n }\n\n // Check cache\n if (options.cache) {\n const cached = ogpCache.get(url);\n if (cached && Date.now() - cached.timestamp < options.cacheTTL) {\n return cached.data;\n }\n }\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), options.timeout);\n\n const response = await fetch(url, {\n headers: {\n \"User-Agent\": options.userAgent,\n Accept: \"text/html,application/xhtml+xml\",\n },\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n console.warn(`Failed to fetch OGP for ${url}: ${response.status}`);\n return null;\n }\n\n const html = await response.text();\n const data = parseOgpFromHtml(html, url);\n\n // Cache the result\n if (options.cache) {\n ogpCache.set(url, { data, timestamp: Date.now() });\n }\n\n return data;\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") {\n console.warn(`Timeout fetching OGP for ${url}`);\n } else {\n console.warn(`Error fetching OGP for ${url}:`, error);\n }\n return null;\n }\n}\n\n/**\n * Create OGP card element.\n */\nfunction createOgpCard(data: OgpData): Element {\n const children: Element[\"children\"] = [];\n\n // Content section\n const contentChildren: Element[\"children\"] = [];\n\n // Title\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-title\"] },\n children: [{ type: \"text\", value: data.title }],\n });\n\n // Description\n if (data.description) {\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-description\"] },\n children: [{ type: \"text\", value: data.description }],\n });\n }\n\n // Meta (favicon + domain)\n const metaChildren: Element[\"children\"] = [];\n\n if (data.favicon) {\n metaChildren.push({\n type: \"element\",\n tagName: \"img\",\n properties: {\n className: [\"ox-ogp-favicon\"],\n src: data.favicon,\n alt: \"\",\n loading: \"lazy\",\n },\n children: [],\n });\n }\n\n metaChildren.push({\n type: \"element\",\n tagName: \"span\",\n properties: { className: [\"ox-ogp-domain\"] },\n children: [{ type: \"text\", value: data.siteName || extractDomain(data.url) }],\n });\n\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-meta\"] },\n children: metaChildren,\n });\n\n children.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-content\"] },\n children: contentChildren,\n });\n\n // Image\n if (data.image) {\n children.push({\n type: \"element\",\n tagName: \"img\",\n properties: {\n className: [\"ox-ogp-image\"],\n src: data.image,\n alt: \"\",\n loading: \"lazy\",\n },\n children: [],\n });\n }\n\n return {\n type: \"element\",\n tagName: \"a\",\n properties: {\n className: [\"ox-ogp-card\"],\n href: isSafeOgpUrl(data.url) ? data.url : \"#\",\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n },\n children,\n };\n}\n\n/**\n * Create fallback element when OGP data is unavailable.\n */\nfunction createFallbackCard(url: string): Element {\n return {\n type: \"element\",\n tagName: \"a\",\n properties: {\n className: [\"ox-ogp-simple\"],\n href: isSafeOgpUrl(url) ? url : \"#\",\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n },\n children: [\n {\n type: \"element\",\n tagName: \"svg\",\n properties: {\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n \"stroke-width\": \"2\",\n },\n children: [\n {\n type: \"element\",\n tagName: \"path\",\n properties: {\n d: \"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3\",\n },\n children: [],\n },\n ],\n },\n { type: \"text\", value: extractDomain(url) },\n ],\n };\n}\n\n/**\n * Collect all OGP URLs from HTML for pre-fetching.\n */\nexport async function collectOgpUrls(html: string): Promise<string[]> {\n const urls: string[] = [];\n const urlPattern = /<ogcard[^>]*\\s+url=[\"']([^\"']+)[\"']/gi;\n\n let match;\n while ((match = urlPattern.exec(html)) !== null) {\n if (isSafeOgpUrl(match[1])) {\n urls.push(match[1]);\n }\n }\n\n return urls;\n}\n\n/**\n * Pre-fetch all OGP data.\n */\nexport async function prefetchOgpData(\n urls: string[],\n options?: OgpOptions,\n): Promise<Map<string, OgpData | null>> {\n const mergedOptions = { ...defaultOptions, ...options };\n const results = new Map<string, OgpData | null>();\n\n await Promise.all(\n urls.map(async (url) => {\n const data = await fetchOgpData(url, mergedOptions);\n results.set(url, data);\n }),\n );\n\n return results;\n}\n\n/**\n * Rehype plugin to transform OgCard components.\n */\nfunction rehypeOgp(ogpDataMap: Map<string, OgpData | null>) {\n return (tree: Root) => {\n const visit = (node: Root | Element) => {\n if (\"children\" in node) {\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i];\n\n if (child.type === \"element\") {\n // Check for <OgCard> component\n if (child.tagName.toLowerCase() === \"ogcard\") {\n const url = getAttribute(child, \"url\");\n\n if (url) {\n const ogpData = ogpDataMap.get(url);\n const cardElement = ogpData ? createOgpCard(ogpData) : createFallbackCard(url);\n node.children[i] = cardElement;\n }\n } else {\n visit(child);\n }\n }\n }\n }\n };\n\n visit(tree);\n };\n}\n\n/**\n * Transform OgCard components in HTML.\n */\nexport async function transformOgp(\n html: string,\n ogpDataMap?: Map<string, OgpData | null>,\n options?: OgpOptions,\n): Promise<string> {\n // If no pre-fetched data, collect and fetch\n let dataMap = ogpDataMap;\n if (!dataMap) {\n const urls = await collectOgpUrls(html);\n dataMap = await prefetchOgpData(urls, options);\n }\n\n const result = await unified()\n .use(rehypeParse, { fragment: true })\n .use(rehypeOgp, dataMap)\n .use(rehypeStringify)\n .process(html);\n\n return String(result);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ogp2.mjs","names":[],"sources":["../src/plugins/ogp.ts"],"sourcesContent":["/**\n * OGP Card Plugin - Link card embedding\n *\n * Transforms <OgCard> components into static link preview cards\n * by fetching OGP metadata at build time.\n */\n\nimport { unified } from \"unified\";\nimport rehypeParse from \"rehype-parse\";\nimport rehypeStringify from \"rehype-stringify\";\nimport type { Root, Element } from \"hast\";\n\nexport interface OgpData {\n url: string;\n title: string;\n description?: string;\n image?: string;\n siteName?: string;\n favicon?: string;\n}\n\nexport interface OgpOptions {\n /** Request timeout in milliseconds. Default: 10000 */\n timeout?: number;\n /** Cache fetched data. Default: true */\n cache?: boolean;\n /** Cache TTL in milliseconds. Default: 3600000 (1 hour) */\n cacheTTL?: number;\n /** User agent for requests */\n userAgent?: string;\n}\n\nconst defaultOptions: Required<OgpOptions> = {\n timeout: 10000,\n cache: true,\n cacheTTL: 3600000,\n userAgent: \"ox-content-ogp-bot/1.0 (compatible; +https://github.com/ubugeeei-prod/ox-content)\",\n};\n\n// Simple in-memory cache\nconst ogpCache = new Map<string, { data: OgpData; timestamp: number }>();\n\nfunction isPrivateIPv4(hostname: string): boolean {\n const parts = hostname.split(\".\").map(Number);\n if (\n parts.length !== 4 ||\n parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)\n ) {\n return false;\n }\n const [a, b] = parts;\n return (\n a === 10 ||\n a === 127 ||\n a === 0 ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && b === 168) ||\n (a === 169 && b === 254)\n );\n}\n\nexport function isSafeOgpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n const host = url.hostname.toLowerCase();\n const ipv6 = host.replace(/^\\[|\\]$/g, \"\");\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return false;\n if (host === \"localhost\" || host.endsWith(\".localhost\")) return false;\n if (\n ipv6.includes(\":\") &&\n (ipv6 === \"::1\" || ipv6.startsWith(\"fc\") || ipv6.startsWith(\"fd\") || ipv6.startsWith(\"fe80\"))\n )\n return false;\n return !isPrivateIPv4(host);\n } catch {\n return false;\n }\n}\n\n/**\n * Get element attribute value.\n */\nfunction getAttribute(el: Element, name: string): string | undefined {\n const value = el.properties?.[name];\n if (typeof value === \"string\") return value;\n if (Array.isArray(value)) return value.join(\" \");\n return undefined;\n}\n\n/**\n * Extract domain from URL.\n */\nfunction extractDomain(url: string): string {\n try {\n const urlObj = new URL(url);\n return urlObj.hostname;\n } catch {\n return url;\n }\n}\n\n/**\n * Get favicon URL for a domain.\n */\nfunction getFaviconUrl(url: string): string {\n try {\n const urlObj = new URL(url);\n // Use Google's favicon service as fallback\n return `https://www.google.com/s2/favicons?domain=${urlObj.hostname}&sz=32`;\n } catch {\n return \"\";\n }\n}\n\n/**\n * Parse OGP metadata from HTML.\n */\nfunction parseOgpFromHtml(html: string, url: string): OgpData {\n const result: OgpData = {\n url,\n title: \"\",\n };\n\n // Extract title\n const titleMatch = html.match(/<title[^>]*>([^<]+)<\\/title>/i);\n const ogTitleMatch =\n html.match(/<meta[^>]*property=[\"']og:title[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:title[\"']/i);\n\n result.title = ogTitleMatch?.[1] || titleMatch?.[1] || extractDomain(url);\n\n // Extract description\n const descMatch =\n html.match(/<meta[^>]*property=[\"']og:description[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:description[\"']/i) ||\n html.match(/<meta[^>]*name=[\"']description[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*name=[\"']description[\"']/i);\n\n if (descMatch) {\n result.description = descMatch[1];\n }\n\n // Extract image\n const imageMatch =\n html.match(/<meta[^>]*property=[\"']og:image[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:image[\"']/i);\n\n if (imageMatch) {\n let imageUrl = imageMatch[1];\n // Handle relative URLs\n if (imageUrl.startsWith(\"/\")) {\n try {\n const urlObj = new URL(url);\n imageUrl = `${urlObj.protocol}//${urlObj.host}${imageUrl}`;\n } catch {\n // Keep as is\n }\n }\n result.image = imageUrl;\n }\n\n // Extract site name\n const siteNameMatch =\n html.match(/<meta[^>]*property=[\"']og:site_name[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:site_name[\"']/i);\n\n if (siteNameMatch) {\n result.siteName = siteNameMatch[1];\n }\n\n // Get favicon\n result.favicon = getFaviconUrl(url);\n\n return result;\n}\n\n/**\n * Fetch OGP data for a URL.\n */\nexport async function fetchOgpData(\n url: string,\n options: Required<OgpOptions>,\n): Promise<OgpData | null> {\n if (!isSafeOgpUrl(url)) {\n return null;\n }\n\n // Check cache\n if (options.cache) {\n const cached = ogpCache.get(url);\n if (cached && Date.now() - cached.timestamp < options.cacheTTL) {\n return cached.data;\n }\n }\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), options.timeout);\n\n const response = await fetch(url, {\n headers: {\n \"User-Agent\": options.userAgent,\n Accept: \"text/html,application/xhtml+xml\",\n },\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n console.warn(`Failed to fetch OGP for ${url}: ${response.status}`);\n return null;\n }\n\n const html = await response.text();\n const data = parseOgpFromHtml(html, url);\n\n // Cache the result\n if (options.cache) {\n ogpCache.set(url, { data, timestamp: Date.now() });\n }\n\n return data;\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") {\n console.warn(`Timeout fetching OGP for ${url}`);\n } else {\n console.warn(`Error fetching OGP for ${url}:`, error);\n }\n return null;\n }\n}\n\n/**\n * Create OGP card element.\n */\nfunction createOgpCard(data: OgpData): Element {\n const children: Element[\"children\"] = [];\n\n // Content section\n const contentChildren: Element[\"children\"] = [];\n\n // Title\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-title\"] },\n children: [{ type: \"text\", value: data.title }],\n });\n\n // Description\n if (data.description) {\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-description\"] },\n children: [{ type: \"text\", value: data.description }],\n });\n }\n\n // Meta (favicon + domain)\n const metaChildren: Element[\"children\"] = [];\n\n if (data.favicon) {\n metaChildren.push({\n type: \"element\",\n tagName: \"img\",\n properties: {\n className: [\"ox-ogp-favicon\"],\n src: data.favicon,\n alt: \"\",\n loading: \"lazy\",\n },\n children: [],\n });\n }\n\n metaChildren.push({\n type: \"element\",\n tagName: \"span\",\n properties: { className: [\"ox-ogp-domain\"] },\n children: [{ type: \"text\", value: data.siteName || extractDomain(data.url) }],\n });\n\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-meta\"] },\n children: metaChildren,\n });\n\n children.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-content\"] },\n children: contentChildren,\n });\n\n // Image\n if (data.image) {\n children.push({\n type: \"element\",\n tagName: \"img\",\n properties: {\n className: [\"ox-ogp-image\"],\n src: data.image,\n alt: \"\",\n loading: \"lazy\",\n },\n children: [],\n });\n }\n\n return {\n type: \"element\",\n tagName: \"a\",\n properties: {\n className: [\"ox-ogp-card\"],\n href: isSafeOgpUrl(data.url) ? data.url : \"#\",\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n },\n children,\n };\n}\n\n/**\n * Create fallback element when OGP data is unavailable.\n */\nfunction createFallbackCard(url: string): Element {\n return {\n type: \"element\",\n tagName: \"a\",\n properties: {\n className: [\"ox-ogp-simple\"],\n href: isSafeOgpUrl(url) ? url : \"#\",\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n },\n children: [\n {\n type: \"element\",\n tagName: \"svg\",\n properties: {\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n \"stroke-width\": \"2\",\n },\n children: [\n {\n type: \"element\",\n tagName: \"path\",\n properties: {\n d: \"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3\",\n },\n children: [],\n },\n ],\n },\n { type: \"text\", value: extractDomain(url) },\n ],\n };\n}\n\n/**\n * Collect all OGP URLs from HTML for pre-fetching.\n */\nexport async function collectOgpUrls(html: string): Promise<string[]> {\n const urls: string[] = [];\n const urlPattern = /<ogcard[^>]*\\s+url=[\"']([^\"']+)[\"']/gi;\n\n let match;\n while ((match = urlPattern.exec(html)) !== null) {\n if (isSafeOgpUrl(match[1])) {\n urls.push(match[1]);\n }\n }\n\n return urls;\n}\n\n/**\n * Pre-fetch all OGP data.\n */\nexport async function prefetchOgpData(\n urls: string[],\n options?: OgpOptions,\n): Promise<Map<string, OgpData | null>> {\n const mergedOptions = { ...defaultOptions, ...options };\n const results = new Map<string, OgpData | null>();\n\n await Promise.all(\n urls.map(async (url) => {\n const data = await fetchOgpData(url, mergedOptions);\n results.set(url, data);\n }),\n );\n\n return results;\n}\n\n/**\n * Rehype plugin to transform OgCard components.\n */\nfunction rehypeOgp(ogpDataMap: Map<string, OgpData | null>) {\n return (tree: Root) => {\n const visit = (node: Root | Element) => {\n if (\"children\" in node) {\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i];\n\n if (child.type === \"element\") {\n // Check for <OgCard> component\n if (child.tagName.toLowerCase() === \"ogcard\") {\n const url = getAttribute(child, \"url\");\n\n if (url) {\n const ogpData = ogpDataMap.get(url);\n const cardElement = ogpData ? createOgpCard(ogpData) : createFallbackCard(url);\n node.children[i] = cardElement;\n }\n } else {\n visit(child);\n }\n }\n }\n }\n };\n\n visit(tree);\n };\n}\n\n/**\n * Transform OgCard components in HTML.\n */\nexport async function transformOgp(\n html: string,\n ogpDataMap?: Map<string, OgpData | null>,\n options?: OgpOptions,\n): Promise<string> {\n // If no pre-fetched data, collect and fetch\n let dataMap = ogpDataMap;\n if (!dataMap) {\n const urls = await collectOgpUrls(html);\n dataMap = await prefetchOgpData(urls, options);\n }\n\n const result = await unified()\n .use(rehypeParse, { fragment: true })\n .use(rehypeOgp, dataMap)\n .use(rehypeStringify)\n .process(html);\n\n return String(result);\n}\n"],"mappings":"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"}
|
package/dist/vitepress-cli.cjs
CHANGED
|
@@ -3,8 +3,8 @@ const require_chunk = require("./chunk.cjs");
|
|
|
3
3
|
const require_vitepress = require("./vitepress.cjs");
|
|
4
4
|
let node_path = require("node:path");
|
|
5
5
|
node_path = require_chunk.__toESM(node_path, 1);
|
|
6
|
-
let node_url = require("node:url");
|
|
7
6
|
let node_fs_promises = require("node:fs/promises");
|
|
7
|
+
let node_url = require("node:url");
|
|
8
8
|
//#region src/vitepress-cli-runtime.ts
|
|
9
9
|
const DEFAULT_CONFIG_FILES = [
|
|
10
10
|
".vitepress/config.ts",
|
package/dist/vitepress-cli.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { i as generateVitePressMigrationConfig } from "./vitepress.mjs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { pathToFileURL } from "node:url";
|
|
5
4
|
import { access, mkdir, writeFile } from "node:fs/promises";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
6
|
//#region src/vitepress-cli-runtime.ts
|
|
7
7
|
const DEFAULT_CONFIG_FILES = [
|
|
8
8
|
".vitepress/config.ts",
|
package/dist/vitepress.cjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const require_napi = require("./napi.cjs");
|
|
1
2
|
//#region src/theme.ts
|
|
2
3
|
/**
|
|
3
4
|
* Default theme configuration.
|
|
@@ -452,36 +453,11 @@ function formatTsValue(value, depth = 0) {
|
|
|
452
453
|
function formatObjectKey(key) {
|
|
453
454
|
return /^[A-Za-z_$][\w$]*$/.test(key) ? key : JSON.stringify(key);
|
|
454
455
|
}
|
|
455
|
-
function toNumber(value) {
|
|
456
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
457
|
-
if (typeof value === "string" && /^\d+$/.test(value)) return Number(value);
|
|
458
|
-
}
|
|
459
|
-
function normalizeHeroImage(value) {
|
|
460
|
-
if (typeof value === "string") return { src: value };
|
|
461
|
-
if (!isRecord(value)) return;
|
|
462
|
-
const src = typeof value.src === "string" && value.src || typeof value.light === "string" && value.light || typeof value.dark === "string" && value.dark;
|
|
463
|
-
if (!src) return;
|
|
464
|
-
return {
|
|
465
|
-
src,
|
|
466
|
-
...typeof value.alt === "string" ? { alt: value.alt } : {},
|
|
467
|
-
...toNumber(value.width) !== void 0 ? { width: toNumber(value.width) } : {},
|
|
468
|
-
...toNumber(value.height) !== void 0 ? { height: toNumber(value.height) } : {}
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
456
|
/**
|
|
472
457
|
* Normalizes VitePress-specific frontmatter into ox-content's entry-page shape.
|
|
473
458
|
*/
|
|
474
459
|
function normalizeVitePressFrontmatter(frontmatter) {
|
|
475
|
-
|
|
476
|
-
if (frontmatter.layout === "home") next.layout = "entry";
|
|
477
|
-
if (isRecord(frontmatter.hero)) {
|
|
478
|
-
const image = normalizeHeroImage(frontmatter.hero.image);
|
|
479
|
-
if (image) next.hero = {
|
|
480
|
-
...frontmatter.hero,
|
|
481
|
-
image
|
|
482
|
-
};
|
|
483
|
-
}
|
|
484
|
-
return next;
|
|
460
|
+
return require_napi.importNapiModuleSync().normalizeVitePressFrontmatter(frontmatter);
|
|
485
461
|
}
|
|
486
462
|
//#endregion
|
|
487
463
|
Object.defineProperty(exports, "convertVitePressNav", {
|
package/dist/vitepress.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitepress.cjs","names":[],"sources":["../src/theme.ts","../src/vitepress.ts"],"sourcesContent":["/**\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 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}\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 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 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);\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\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 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 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 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 { 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\nfunction toNumber(value: unknown): number | undefined {\n if (typeof value === \"number\" && Number.isFinite(value)) {\n return value;\n }\n\n if (typeof value === \"string\" && /^\\d+$/.test(value)) {\n return Number(value);\n }\n\n return undefined;\n}\n\nfunction normalizeHeroImage(value: unknown): Record<string, unknown> | undefined {\n if (typeof value === \"string\") {\n return { src: value };\n }\n\n if (!isRecord(value)) {\n return undefined;\n }\n\n const src =\n (typeof value.src === \"string\" && value.src) ||\n (typeof value.light === \"string\" && value.light) ||\n (typeof value.dark === \"string\" && value.dark);\n\n if (!src) {\n return undefined;\n }\n\n return {\n src,\n ...(typeof value.alt === \"string\" ? { alt: value.alt } : {}),\n ...(toNumber(value.width) !== undefined ? { width: toNumber(value.width) } : {}),\n ...(toNumber(value.height) !== undefined ? { height: toNumber(value.height) } : {}),\n };\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 const next = { ...frontmatter };\n\n if (frontmatter.layout === \"home\") {\n next.layout = \"entry\";\n }\n\n if (isRecord(frontmatter.hero)) {\n const image = normalizeHeroImage(frontmatter.hero.image);\n if (image) {\n next.hero = {\n ...frontmatter.hero,\n image,\n };\n }\n }\n\n return next;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vitepress.cjs","names":["importNapiModuleSync"],"sources":["../src/theme.ts","../src/vitepress.ts"],"sourcesContent":["/**\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 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}\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 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 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);\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\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 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 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 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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { n as importNapiModuleSync } from "./napi.mjs";
|
|
1
2
|
//#region src/theme.ts
|
|
2
3
|
/**
|
|
3
4
|
* Default theme configuration.
|
|
@@ -452,36 +453,11 @@ function formatTsValue(value, depth = 0) {
|
|
|
452
453
|
function formatObjectKey(key) {
|
|
453
454
|
return /^[A-Za-z_$][\w$]*$/.test(key) ? key : JSON.stringify(key);
|
|
454
455
|
}
|
|
455
|
-
function toNumber(value) {
|
|
456
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
457
|
-
if (typeof value === "string" && /^\d+$/.test(value)) return Number(value);
|
|
458
|
-
}
|
|
459
|
-
function normalizeHeroImage(value) {
|
|
460
|
-
if (typeof value === "string") return { src: value };
|
|
461
|
-
if (!isRecord(value)) return;
|
|
462
|
-
const src = typeof value.src === "string" && value.src || typeof value.light === "string" && value.light || typeof value.dark === "string" && value.dark;
|
|
463
|
-
if (!src) return;
|
|
464
|
-
return {
|
|
465
|
-
src,
|
|
466
|
-
...typeof value.alt === "string" ? { alt: value.alt } : {},
|
|
467
|
-
...toNumber(value.width) !== void 0 ? { width: toNumber(value.width) } : {},
|
|
468
|
-
...toNumber(value.height) !== void 0 ? { height: toNumber(value.height) } : {}
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
456
|
/**
|
|
472
457
|
* Normalizes VitePress-specific frontmatter into ox-content's entry-page shape.
|
|
473
458
|
*/
|
|
474
459
|
function normalizeVitePressFrontmatter(frontmatter) {
|
|
475
|
-
|
|
476
|
-
if (frontmatter.layout === "home") next.layout = "entry";
|
|
477
|
-
if (isRecord(frontmatter.hero)) {
|
|
478
|
-
const image = normalizeHeroImage(frontmatter.hero.image);
|
|
479
|
-
if (image) next.hero = {
|
|
480
|
-
...frontmatter.hero,
|
|
481
|
-
image
|
|
482
|
-
};
|
|
483
|
-
}
|
|
484
|
-
return next;
|
|
460
|
+
return importNapiModuleSync().normalizeVitePressFrontmatter(frontmatter);
|
|
485
461
|
}
|
|
486
462
|
//#endregion
|
|
487
463
|
export { normalizeVitePressFrontmatter as a, mergeThemes as c, generateVitePressMigrationConfig as i, resolveTheme as l, convertVitePressSidebar as n, defaultTheme as o, fromVitePressConfig as r, defineTheme as s, convertVitePressNav as t, themeToNapi as u };
|
package/dist/vitepress.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitepress.mjs","names":[],"sources":["../src/theme.ts","../src/vitepress.ts"],"sourcesContent":["/**\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 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}\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 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 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);\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\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 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 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 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 { 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\nfunction toNumber(value: unknown): number | undefined {\n if (typeof value === \"number\" && Number.isFinite(value)) {\n return value;\n }\n\n if (typeof value === \"string\" && /^\\d+$/.test(value)) {\n return Number(value);\n }\n\n return undefined;\n}\n\nfunction normalizeHeroImage(value: unknown): Record<string, unknown> | undefined {\n if (typeof value === \"string\") {\n return { src: value };\n }\n\n if (!isRecord(value)) {\n return undefined;\n }\n\n const src =\n (typeof value.src === \"string\" && value.src) ||\n (typeof value.light === \"string\" && value.light) ||\n (typeof value.dark === \"string\" && value.dark);\n\n if (!src) {\n return undefined;\n }\n\n return {\n src,\n ...(typeof value.alt === \"string\" ? { alt: value.alt } : {}),\n ...(toNumber(value.width) !== undefined ? { width: toNumber(value.width) } : {}),\n ...(toNumber(value.height) !== undefined ? { height: toNumber(value.height) } : {}),\n };\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 const next = { ...frontmatter };\n\n if (frontmatter.layout === \"home\") {\n next.layout = \"entry\";\n }\n\n if (isRecord(frontmatter.hero)) {\n const image = normalizeHeroImage(frontmatter.hero.image);\n if (image) {\n next.hero = {\n ...frontmatter.hero,\n image,\n };\n }\n }\n\n return next;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vitepress.mjs","names":[],"sources":["../src/theme.ts","../src/vitepress.ts"],"sourcesContent":["/**\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 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}\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 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 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);\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\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 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 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 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":";;;;;;AAsMA,MAAa,eAA4B;CACvC,MAAM;CACN,QAAQ;EACN,SAAS;EACT,cAAc;EACd,YAAY;EACZ,eAAe;EACf,MAAM;EACN,WAAW;EACX,QAAQ;EACR,gBAAgB;EAChB,UAAU;EACX;CACD,YAAY;EACV,SAAS;EACT,cAAc;EACd,YAAY;EACZ,eAAe;EACf,MAAM;EACN,WAAW;EACX,QAAQ;EACR,gBAAgB;EAChB,UAAU;EACX;CACD,OAAO;EACL,MAAM;EACN,MAAM;EACP;CACD,WAAW,EACT,MAAM,WACP;CACD,QAAQ;EACN,cAAc;EACd,cAAc;EACd,iBAAiB;EAClB;CACD,QAAQ;EACN,MAAM,KAAA;EACN,WAAW,KAAA;EACX,UAAU,KAAA;EACV,kBAAkB;EAClB,WAAW;EACX,YAAY;EACb;CACD,QAAQ;EACN,SAAS,KAAA;EACT,WAAW,KAAA;EACZ;CACD,aAAa,EAAE;CACf,OAAO,EAAE;CACT,KAAK;CACL,IAAI;CACL;;;;AAKD,SAAS,UAA6C,QAAW,QAAuB;CACtF,MAAM,SAAS,EAAE,GAAG,QAAQ;CAE5B,KAAK,MAAM,OAAO,OAAO,KAAK,OAAO,EAAiB;EACpD,MAAM,cAAc,OAAO;EAC3B,MAAM,cAAc,OAAO;EAE3B,IACE,gBAAgB,KAAA,KAChB,OAAO,gBAAgB,YACvB,gBAAgB,QAChB,CAAC,MAAM,QAAQ,YAAY,IAC3B,OAAO,gBAAgB,YACvB,gBAAgB,QAChB,CAAC,MAAM,QAAQ,YAAY,EAE3B,OAAO,OAAO,UACZ,aACA,YACD;OACI,IAAI,gBAAgB,KAAA,GACzB,OAAO,OAAO;;CAIlB,OAAO;;;;;;;;;;;;;;;;;;AAmBT,SAAgB,YAAY,QAAkC;CAC5D,OAAO;;;;;;;;;;;AAYT,SAAgB,YAAY,GAAG,QAAoC;CACjE,IAAI,OAAO,WAAW,GACpB,OAAO,EAAE,GAAG,cAAc;CAG5B,IAAI,SAAsB,EAAE;CAE5B,KAAK,MAAM,SAAS,QAClB,SAAS,UACP,QACA,MACD;CAGH,OAAO;;;;;AAMT,SAAgB,aAAa,QAA2C;CACtE,IAAI,CAAC,QACH,OAAO,aAAa,aAAa;CAInC,MAAM,QAAuB,EAAE;CAC/B,IAAI,UAAmC;CAEvC,OAAO,SAAS;EACd,MAAM,QAAQ,QAAQ;EACtB,UAAU,QAAQ;;CAIpB,IAAI,MAAM,OAAO,gBAAgB,MAAM,IAAI,SAAS,WAClD,MAAM,QAAQ,aAAa;CAI7B,MAAM,SAAS,YAAY,GAAG,MAAM;CAGpC,OAAO;EACL,MAAM,OAAO,QAAQ;EACrB,QAAQ,OAAO,UAAU,aAAa;EACtC,YAAY,OAAO,cAAc,aAAa;EAC9C,OAAO,OAAO,SAAS,aAAa;EACpC,WAAW,OAAO,aAAa,aAAa;EAC5C,QAAQ,OAAO,UAAU,aAAa;EACtC,QAAQ,OAAO,UAAU,aAAa;EACtC,QAAQ,OAAO,UAAU,aAAa;EACtC,aAAa,OAAO,eAAe,aAAa;EAChD,SAAS,OAAO,WAAW,EAAE;EAC7B,OAAO,OAAO,SAAS,EAAE;EACzB,KAAK,OAAO,OAAO;EACnB,IAAI,OAAO,MAAM;EAClB;;;;;AAMH,SAAgB,YAAY,OAA6C;CACvE,MAAM,cAAc,kBAAkB,MAAM,YAAY;CAExD,OAAO;EACL,QAAQ,MAAM,OAAO,UACjB;GACE,SAAS,MAAM,OAAO;GACtB,cAAc,MAAM,OAAO;GAC3B,YAAY,MAAM,OAAO;GACzB,eAAe,MAAM,OAAO;GAC5B,MAAM,MAAM,OAAO;GACnB,WAAW,MAAM,OAAO;GACxB,QAAQ,MAAM,OAAO;GACrB,gBAAgB,MAAM,OAAO;GAC7B,UAAU,MAAM,OAAO;GACxB,GACD,KAAA;EACJ,YAAY,MAAM,WAAW,UACzB;GACE,SAAS,MAAM,WAAW;GAC1B,cAAc,MAAM,WAAW;GAC/B,YAAY,MAAM,WAAW;GAC7B,eAAe,MAAM,WAAW;GAChC,MAAM,MAAM,WAAW;GACvB,WAAW,MAAM,WAAW;GAC5B,QAAQ,MAAM,WAAW;GACzB,gBAAgB,MAAM,WAAW;GACjC,UAAU,MAAM,WAAW;GAC5B,GACD,KAAA;EACJ,OAAO,MAAM,MAAM,OACf;GACE,MAAM,MAAM,MAAM;GAClB,MAAM,MAAM,MAAM;GACnB,GACD,KAAA;EACJ,WAAW,MAAM,UAAU,OACvB,EACE,MAAM,MAAM,UAAU,MACvB,GACD,KAAA;EACJ,QAAQ,MAAM,OAAO,eACjB;GACE,cAAc,MAAM,OAAO;GAC3B,cAAc,MAAM,OAAO;GAC3B,iBAAiB,MAAM,OAAO;GAC/B,GACD,KAAA;EACJ,QACE,MAAM,OAAO,QAAQ,MAAM,OAAO,aAAa,MAAM,OAAO,WACxD;GACE,MAAM,MAAM,OAAO;GACnB,WAAW,MAAM,OAAO;GACxB,UAAU,MAAM,OAAO;GACvB,kBAAkB,MAAM,OAAO;GAC/B,WAAW,MAAM,OAAO;GACxB,YAAY,MAAM,OAAO;GAC1B,GACD,KAAA;EACN,QACE,MAAM,OAAO,WAAW,MAAM,OAAO,YACjC;GACE,SAAS,MAAM,OAAO;GACtB,WAAW,MAAM,OAAO;GACzB,GACD,KAAA;EACN;EACA,OAAO,OAAO,KAAK,MAAM,MAAM,CAAC,SAAS,IAAI,MAAM,QAAQ,KAAA;EAC3D,KAAK,MAAM,OAAO,KAAA;EAClB,IAAI,MAAM,MAAM,KAAA;EACjB;;AAGH,SAAS,kBAAkB,OAAiD;CAC1E,IAAI,MAAM,QAAQ,MAAM,EAAE;EACxB,MAAM,QAAQ,MAAM,KAAK,SAAS;GAGhC,OAAO;IAAE,MAFI,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO,KAAA;IAE1C,SADC,OAAO,KAAK,SAAS,WAAW,KAAK,KAAK,MAAM,KAAA;IACxC,MAAM,KAAK;IAAM,WAAW,KAAK;IAAW;IACpE;EACF,OAAO,MAAM,SAAS,IAAI,EAAE,OAAO,OAAO,GAAG,KAAA;;CAG/C,OAAO,MAAM,UAAU,MAAM,WAAW,MAAM,UAC1C;EAAE,QAAQ,MAAM;EAAQ,SAAS,MAAM;EAAS,SAAS,MAAM;EAAS,GACxE,KAAA;;;;AC/YN,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;;AAG7E,SAAS,eAAe,OAAwB;CAC9C,OAAO,uBAAuB,KAAK,MAAM,IAAI,MAAM,WAAW,KAAK;;AAGrE,SAAS,UAAU,OAAqD;CACtE,MAAM,QAAQ,sBAAsB,KAAK,MAAM;CAC/C,OAAO;EACL,UAAU,QAAQ,MAAM;EACxB,QAAQ,QAAQ,MAAM;EACvB;;AAGH,SAAS,sBAAsB,OAAuB;CACpD,MAAM,EAAE,aAAa,UAAU,MAAM,MAAM,CAAC;CAC5C,IAAI,aAAa,YAAY;CAE7B,IAAI,CAAC,WAAW,WAAW,IAAI,EAC7B,aAAa,IAAI;CAGnB,aAAa,WACV,QAAQ,yCAAyC,IAAI,CACrD,QAAQ,6BAA6B,GAAG;CAE3C,IAAI,eAAe,KACjB,aAAa,WAAW,QAAQ,QAAQ,GAAG;CAG7C,OAAO,cAAc;;AAGvB,SAAS,YAAY,OAAuB;CAC1C,OAAO,MACJ,QAAQ,iBAAiB,GAAG,SAAiB,IAAI,KAAK,aAAa,GAAG,CACtE,QAAQ,WAAW,SAAS,KAAK,aAAa,CAAC;;AAGpD,SAAS,cAAc,OAAuB;CAC5C,MAAM,aAAa,sBAAsB,MAAM;CAC/C,IAAI,eAAe,KACjB,OAAO;CAIT,OAAO,YADS,WAAW,MAAM,IAAI,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI,OACpC;;AAG7B,SAAS,oBAAoB,OAAuB;CAMlD,OAAO,YALS,MACb,QAAQ,cAAc,GAAG,CACzB,MAAM,IAAI,CACV,OAAO,QAAQ,CACf,KACuB,IAAI,QAAQ;;AAGxC,SAAS,iBAAiB,MAA0B,MAAiC;CACnF,MAAM,QAAQ,MAAM,MAAM,IAAI,cAAc,KAAK;CAEjD,IAAI,eAAe,KAAK,IAAI,KAAK,WAAW,IAAI,EAC9C,OAAO;EAAE;EAAO,MAAM;EAAM;CAG9B,MAAM,EAAE,WAAW,UAAU,KAAK;CAClC,MAAM,OAAO,sBAAsB,KAAK;CAExC,OAAO,SAAS;EAAE;EAAO;EAAM,MAAM,GAAG,OAAO;EAAU,GAAG;EAAE;EAAO;EAAM;;AAG7E,SAAS,sBAAsB,OAAiD;CAC9E,MAAM,uBAAO,IAAI,KAAa;CAC9B,MAAM,OAA4B,EAAE;CAEpC,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,MAAM,GAAG,KAAK,MAAM,IAAI,KAAK,QAAQ,GAAG,IAAI,KAAK,QAAQ;EAC/D,IAAI,KAAK,IAAI,IAAI,EACf;EAEF,KAAK,IAAI,IAAI;EACb,KAAK,KAAK,KAAK;;CAGjB,OAAO;;AAGT,SAAS,uBAAuB,QAAoD;CAClF,MAAM,yBAAS,IAAI,KAAkC;CACrD,MAAM,gBAA0B,EAAE;CAElC,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,MAAM,MAAM,WAAW,GACzB;EAGF,IAAI,CAAC,OAAO,IAAI,MAAM,MAAM,EAAE;GAC5B,OAAO,IAAI,MAAM,OAAO,EAAE,CAAC;GAC3B,cAAc,KAAK,MAAM,MAAM;;EAGjC,OAAO,IAAI,MAAM,MAAM,CAAE,KAAK,GAAG,MAAM,MAAM;;CAG/C,OAAO,cAAc,KAAK,WAAW;EACnC;EACA,OAAO,sBAAsB,OAAO,IAAI,MAAM,IAAI,EAAE,CAAC;EACtD,EAAE;;AAGL,SAAS,oBAAoB,OAAoD;CAC/E,MAAM,QAA6B,EAAE;CAErC,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,KAAK,MACP,MAAM,KAAK,iBAAiB,KAAK,MAAM,KAAK,KAAK,CAAC;EAGpD,IAAI,KAAK,OAAO,QACd,MAAM,KAAK,GAAG,oBAAoB,KAAK,MAAM,CAAC;;CAIlD,OAAO,sBAAsB,MAAM;;AAGrC,SAAS,qBACP,OACA,eACsB;CACtB,MAAM,SAA+B,EAAE;CACvC,MAAM,YAAiC,EAAE;CAEzC,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,KAAK,MACP,UAAU,KAAK,iBAAiB,KAAK,MAAM,KAAK,KAAK,CAAC;EAGxD,IAAI,KAAK,OAAO,QAAQ;GACtB,MAAM,WAAW,oBAAoB,KAAK,MAAM;GAChD,IAAI,SAAS,SAAS,GACpB,OAAO,KAAK;IACV,OAAO,KAAK,MAAM,MAAM,IAAI;IAC5B,OAAO;IACR,CAAC;;;CAKR,IAAI,UAAU,SAAS,GACrB,OAAO,QAAQ;EACb,OAAO;EACP,OAAO,sBAAsB,UAAU;EACxC,CAAC;CAGJ,OAAO;;AAGT,SAAS,gBAAgB,OAAgD;CACvE,MAAM,QAA6B,EAAE;CAErC,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,KAAK,MACP,MAAM,KAAK,iBAAiB,KAAK,MAAM,KAAK,KAAK,CAAC;EAGpD,IAAI,KAAK,OAAO,QACd,MAAM,KAAK,GAAG,gBAAgB,KAAK,MAAM,CAAC;;CAI9C,OAAO,sBAAsB,MAAM;;AAGrC,SAAS,eAAe,MAA8D;CACpF,IAAI,CAAC,MACH;CAGF,IAAI,OAAO,SAAS,UAClB,OAAO;CAGT,OAAO,KAAK,SAAS,KAAK,QAAQ,KAAK;;AAGzC,SAAS,oBAAoB,MAA4D;CACvF,MAAM,aAAa,KAAK,MAAM,CAAC,aAAa;CAE5C,IAAI,eAAe,UAAU,OAAO;CACpC,IAAI,eAAe,WAAW,OAAO;CACrC,IAAI,eAAe,aAAa,eAAe,OAAO,eAAe,aACnE,OAAO;;AAMX,SAAS,cAAc,aAAwE;CAC7F,IAAI,CAAC,aACH;CAGF,MAAM,OAAO,eAAe,YAAY,KAAK;CAC7C,MAAM,cAAc,OAAO,aACxB,YAAY,eAAe,EAAE,EAC3B,KAAK,SAAS;EACb,MAAM,MAAM,oBAAoB,KAAK,KAAK;EAC1C,OAAO,MAAM,CAAC,KAAK,KAAK,KAAK,GAAG;GAChC,CACD,QAAQ,UAAqC,UAAU,KAAK,CAChE;CAED,MAAM,QAAqB;EACzB,GAAI,OACA,EACE,QAAQ,EACN,MACD,EACF,GACD,EAAE;EACN,GAAI,YAAY,QAAQ,WAAW,YAAY,QAAQ,YACnD,EACE,QAAQ;GACN,SAAS,YAAY,OAAO;GAC5B,WAAW,YAAY,OAAO;GAC/B,EACF,GACD,EAAE;EACN,GAAI,OAAO,KAAK,YAAY,CAAC,SAAS,IAClC,EACE,aACD,GACD,EAAE;EACP;CAED,OAAO,QAAQ,OAAO,KAAK,YAAY,CAAC,SAAS,KAAK,YAAY,SAC9D,YAAY,MAAM,GAClB,KAAA;;AAGN,SAAS,gBAAgB,QAA6C;CACpE,MAAM,YAAY,OAAO,aAAa;CACtC,IAAI,OAAO,cAAc,YAAY,UAAU,MAAM,EACnD,OAAO;CAGT,OAAO,OAAO;;AAGhB,SAAS,sBACP,aACA,WACkB;CAClB,MAAM,YACJ,UAAU,QAAQ,QACd,QACA;EACE,GAAI,OAAO,YAAY,QAAQ,WAAW,YAAY,MAAM,EAAE;EAC9D,GAAI,OAAO,UAAU,QAAQ,WAAW,UAAU,MAAM,EAAE;EAC1D,OACE,OAAO,YAAY,QAAQ,YAC3B,OAAO,UAAU,QAAQ,YACzB,YAAY,IAAI,SAChB,UAAU,IAAI,QACV,YAAY,YAAY,YAAY,IAAI,OAAO,UAAU,IAAI,MAAM,CAAC,GACpE,OAAO,UAAU,QAAQ,YAAY,UAAU,IAAI,QACjD,UAAU,IAAI,QACd,OAAO,YAAY,QAAQ,WACzB,YAAY,IAAI,QAChB,KAAA;EACX;CAEP,MAAM,eACJ,UAAU,WAAW,QACjB,QACA,OAAO,UAAU,WAAW,WAC1B;EACE,GAAI,OAAO,YAAY,WAAW,WAAW,YAAY,SAAS,EAAE;EACpE,GAAG,UAAU;EACd,GACD,YAAY;CAEpB,OAAO;EACL,GAAG;EACH,GAAG;EACH,KAAK;EACL,QAAQ;EACT;;;;;;AAOH,SAAgB,wBAAwB,SAAiD;CACvF,IAAI,MAAM,QAAQ,QAAQ,EACxB,OAAO,uBAAuB,qBAAqB,SAAS,QAAQ,CAAC;CAOvE,OAAO,uBAJQ,OAAO,QAAQ,QAAQ,CAAC,SAAS,CAAC,KAAK,WACpD,qBAAqB,OAAO,oBAAoB,IAAI,CAAC,CAGnB,CAAC;;;;;;AAOvC,SAAgB,oBAAoB,KAA+C;CACjF,MAAM,SAA+B,EAAE;CACvC,MAAM,YAAiC,EAAE;CAEzC,KAAK,MAAM,QAAQ,KAAK;EACtB,IAAI,KAAK,MACP,UAAU,KAAK,iBAAiB,KAAK,MAAM,KAAK,KAAK,CAAC;EAGxD,IAAI,KAAK,OAAO,QAAQ;GACtB,MAAM,WAAW,gBAAgB,KAAK,MAAM;GAC5C,IAAI,SAAS,SAAS,GACpB,OAAO,KAAK;IACV,OAAO,KAAK,MAAM,MAAM,IAAI;IAC5B,OAAO;IACR,CAAC;;;CAKR,IAAI,UAAU,SAAS,GACrB,OAAO,QAAQ;EACb,OAAO;EACP,OAAO,sBAAsB,UAAU;EACxC,CAAC;CAGJ,OAAO,uBAAuB,OAAO;;;;;AAMvC,SAAgB,oBACd,QACA,YAA8B,EAAE,EACd;CAClB,MAAM,QAAQ,cAAc,OAAO,YAAY;CAC/C,MAAM,aAAa,OAAO,aAAa,UACnC,wBAAwB,OAAO,YAAY,QAAQ,GACnD,OAAO,aAAa,MAClB,oBAAoB,OAAO,YAAY,IAAI,GAC3C,KAAA;CAkBN,OAAO,sBAAsB;EAf3B,GAAI,OAAO,OAAO,EAAE,MAAM,OAAO,MAAM,GAAG,EAAE;EAC5C,GAAI,OAAO,aAAa,QAAQ,cAC5B,EACE,QAAQ,EACN,aAAa,OAAO,YAAY,OAAO,aACxC,EACF,GACD,EAAE;EACN,KAAK;GACH,GAAI,gBAAgB,OAAO,GAAG,EAAE,UAAU,gBAAgB,OAAO,EAAE,GAAG,EAAE;GACxE,GAAI,QAAQ,EAAE,OAAO,GAAG,EAAE;GAC1B,GAAI,aAAa,EAAE,YAAY,GAAG,EAAE;GACrC;EAGkC,EAAE,UAAU;;;;;;;;AASnD,SAAgB,iCACd,QACA,YAA8B,EAAE,EAChC,UAAmD,EAAE,EAC7C;CACR,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,MAAM,WAAW,oBAAoB,QAAQ,UAAU;CAEvD,OAAO,yCAAyC,KAAK,UAAU,aAAa,CAAC;;iBAE9D,cAAc,SAAS,CAAC;;;;;AAMzC,SAAS,cAAc,OAAgB,QAAQ,GAAW;CACxD,IAAI,UAAU,KAAA,GACZ,OAAO;CAGT,IAAI,UAAU,QAAQ,OAAO,UAAU,aAAa,OAAO,UAAU,UACnE,OAAO,KAAK,UAAU,MAAM;CAG9B,IAAI,OAAO,UAAU,UACnB,OAAO,KAAK,UAAU,MAAM;CAG9B,IAAI,MAAM,QAAQ,MAAM,EAAE;EACxB,IAAI,MAAM,WAAW,GACnB,OAAO;EAGT,MAAM,SAAS,KAAK,OAAO,QAAQ,EAAE;EACrC,MAAM,gBAAgB,KAAK,OAAO,MAAM;EACxC,OAAO,MAAM,MAAM,KAAK,SAAS,GAAG,SAAS,cAAc,MAAM,QAAQ,EAAE,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI,cAAc;;CAG/G,IAAI,SAAS,MAAM,EAAE;EACnB,MAAM,UAAU,OAAO,QAAQ,MAAM,CAAC,QAAQ,GAAG,gBAAgB,eAAe,KAAA,EAAU;EAC1F,IAAI,QAAQ,WAAW,GACrB,OAAO;EAGT,MAAM,SAAS,KAAK,OAAO,QAAQ,EAAE;EACrC,MAAM,gBAAgB,KAAK,OAAO,MAAM;EACxC,OAAO,MAAM,QACV,KACE,CAAC,KAAK,gBACL,GAAG,SAAS,gBAAgB,IAAI,CAAC,IAAI,cAAc,YAAY,QAAQ,EAAE,CAAC,GAC7E,CACA,KAAK,KAAK,CAAC,IAAI,cAAc;;CAGlC,OAAO;;AAGT,SAAS,gBAAgB,KAAqB;CAC5C,OAAO,qBAAqB,KAAK,IAAI,GAAG,MAAM,KAAK,UAAU,IAAI;;;;;AAMnE,SAAgB,8BACd,aACyB;CACzB,OAAO,sBAAsB,CAAC,8BAA8B,YAAY"}
|