@ox-content/vite-plugin 2.16.0 → 2.26.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 +371 -367
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -4
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +29 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +371 -367
- 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/tabs.cjs +14 -154
- package/dist/tabs.cjs.map +1 -1
- package/dist/tabs2.mjs +14 -152
- package/dist/tabs2.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/dist/youtube.cjs +10 -89
- package/dist/youtube.cjs.map +1 -1
- package/dist/youtube2.mjs +10 -87
- package/dist/youtube2.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/tabs.cjs
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
const require_chunk = require("./chunk.cjs");
|
|
2
|
-
|
|
3
|
-
let rehype_parse = require("rehype-parse");
|
|
4
|
-
rehype_parse = require_chunk.__toESM(rehype_parse, 1);
|
|
5
|
-
let rehype_stringify = require("rehype-stringify");
|
|
6
|
-
rehype_stringify = require_chunk.__toESM(rehype_stringify, 1);
|
|
2
|
+
const require_napi = require("./napi.cjs");
|
|
7
3
|
//#region src/plugins/tabs.ts
|
|
8
4
|
/**
|
|
9
5
|
* Tabs Plugin - Pure CSS implementation
|
|
10
6
|
*
|
|
11
|
-
* Transforms <Tabs>/<Tab> components into accessible HTML
|
|
12
|
-
*
|
|
7
|
+
* Transforms <Tabs>/<Tab> components into accessible HTML with CSS :has()
|
|
8
|
+
* based tab switching (no JavaScript required).
|
|
9
|
+
*
|
|
10
|
+
* The HTML rewrite is performed in Rust (`transformTabsEmbeds` in
|
|
11
|
+
* @ox-content/napi), replacing the previous rehype parse/stringify round-trip.
|
|
12
|
+
* The per-build group counter (consumed by `generateTabsCSS`) stays here and is
|
|
13
|
+
* advanced by the number of groups the Rust transform reports, so CSS
|
|
14
|
+
* generation still covers exactly the groups that were emitted.
|
|
13
15
|
*/
|
|
14
16
|
var tabs_exports = /* @__PURE__ */ require_chunk.__exportAll({
|
|
15
17
|
generateTabsCSS: () => generateTabsCSS,
|
|
@@ -18,161 +20,19 @@ var tabs_exports = /* @__PURE__ */ require_chunk.__exportAll({
|
|
|
18
20
|
});
|
|
19
21
|
let tabGroupCounter = 0;
|
|
20
22
|
/**
|
|
21
|
-
* Reset tab group counter (for testing).
|
|
23
|
+
* Reset tab group counter (for testing and per dev-server request).
|
|
22
24
|
*/
|
|
23
25
|
function resetTabGroupCounter() {
|
|
24
26
|
tabGroupCounter = 0;
|
|
25
27
|
}
|
|
26
28
|
/**
|
|
27
|
-
* Get element attribute value.
|
|
28
|
-
*/
|
|
29
|
-
function getAttribute(el, name) {
|
|
30
|
-
const value = el.properties?.[name];
|
|
31
|
-
if (typeof value === "string") return value;
|
|
32
|
-
if (Array.isArray(value)) return value.join(" ");
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Parse Tab elements from Tabs children.
|
|
36
|
-
*/
|
|
37
|
-
function parseTabChildren(children) {
|
|
38
|
-
const tabs = [];
|
|
39
|
-
for (const child of children) {
|
|
40
|
-
if (child.type !== "element") continue;
|
|
41
|
-
if (child.tagName.toLowerCase() === "tab") {
|
|
42
|
-
const label = getAttribute(child, "label") || `Tab ${tabs.length + 1}`;
|
|
43
|
-
tabs.push({
|
|
44
|
-
label,
|
|
45
|
-
content: child.children.filter((c) => c.type === "element" || c.type === "text")
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return tabs;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Create the HTML structure for tabs.
|
|
53
|
-
*/
|
|
54
|
-
function createTabsElement(tabs, groupId) {
|
|
55
|
-
const children = [];
|
|
56
|
-
const headerChildren = [];
|
|
57
|
-
tabs.forEach((tab, index) => {
|
|
58
|
-
const inputId = `ox-tab-${groupId}-${index}`;
|
|
59
|
-
headerChildren.push({
|
|
60
|
-
type: "element",
|
|
61
|
-
tagName: "input",
|
|
62
|
-
properties: {
|
|
63
|
-
type: "radio",
|
|
64
|
-
name: `ox-tabs-${groupId}`,
|
|
65
|
-
id: inputId,
|
|
66
|
-
checked: index === 0 ? true : void 0
|
|
67
|
-
},
|
|
68
|
-
children: []
|
|
69
|
-
});
|
|
70
|
-
headerChildren.push({
|
|
71
|
-
type: "element",
|
|
72
|
-
tagName: "label",
|
|
73
|
-
properties: { htmlFor: inputId },
|
|
74
|
-
children: [{
|
|
75
|
-
type: "text",
|
|
76
|
-
value: tab.label
|
|
77
|
-
}]
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
children.push({
|
|
81
|
-
type: "element",
|
|
82
|
-
tagName: "div",
|
|
83
|
-
properties: { className: ["ox-tabs-header"] },
|
|
84
|
-
children: headerChildren
|
|
85
|
-
});
|
|
86
|
-
tabs.forEach((tab, index) => {
|
|
87
|
-
children.push({
|
|
88
|
-
type: "element",
|
|
89
|
-
tagName: "div",
|
|
90
|
-
properties: {
|
|
91
|
-
className: ["ox-tab-panel"],
|
|
92
|
-
"data-tab": String(index)
|
|
93
|
-
},
|
|
94
|
-
children: tab.content
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
return {
|
|
98
|
-
type: "element",
|
|
99
|
-
tagName: "div",
|
|
100
|
-
properties: {
|
|
101
|
-
className: ["ox-tabs"],
|
|
102
|
-
"data-group": groupId
|
|
103
|
-
},
|
|
104
|
-
children
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Create fallback HTML using <details> elements.
|
|
109
|
-
*/
|
|
110
|
-
function createFallbackElement(tabs) {
|
|
111
|
-
const children = [];
|
|
112
|
-
tabs.forEach((tab, index) => {
|
|
113
|
-
children.push({
|
|
114
|
-
type: "element",
|
|
115
|
-
tagName: "details",
|
|
116
|
-
properties: { open: index === 0 ? true : void 0 },
|
|
117
|
-
children: [{
|
|
118
|
-
type: "element",
|
|
119
|
-
tagName: "summary",
|
|
120
|
-
properties: {},
|
|
121
|
-
children: [{
|
|
122
|
-
type: "text",
|
|
123
|
-
value: tab.label
|
|
124
|
-
}]
|
|
125
|
-
}, {
|
|
126
|
-
type: "element",
|
|
127
|
-
tagName: "div",
|
|
128
|
-
properties: { className: ["ox-tabs-fallback-content"] },
|
|
129
|
-
children: tab.content
|
|
130
|
-
}]
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
return {
|
|
134
|
-
type: "element",
|
|
135
|
-
tagName: "noscript",
|
|
136
|
-
properties: {},
|
|
137
|
-
children: [{
|
|
138
|
-
type: "element",
|
|
139
|
-
tagName: "div",
|
|
140
|
-
properties: { className: ["ox-tabs-fallback"] },
|
|
141
|
-
children
|
|
142
|
-
}]
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Rehype plugin to transform Tabs components.
|
|
147
|
-
*/
|
|
148
|
-
function rehypeTabs() {
|
|
149
|
-
return (tree) => {
|
|
150
|
-
const visit = (node) => {
|
|
151
|
-
if ("children" in node) for (let i = 0; i < node.children.length; i++) {
|
|
152
|
-
const child = node.children[i];
|
|
153
|
-
if (child.type === "element") if (child.tagName.toLowerCase() === "tabs") {
|
|
154
|
-
const tabs = parseTabChildren(child.children);
|
|
155
|
-
if (tabs.length > 0) {
|
|
156
|
-
const wrapper = {
|
|
157
|
-
type: "element",
|
|
158
|
-
tagName: "div",
|
|
159
|
-
properties: { className: ["ox-tabs-container"] },
|
|
160
|
-
children: [createTabsElement(tabs, String(tabGroupCounter++)), createFallbackElement(tabs)]
|
|
161
|
-
};
|
|
162
|
-
node.children[i] = wrapper;
|
|
163
|
-
}
|
|
164
|
-
} else visit(child);
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
visit(tree);
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
29
|
* Transform Tabs components in HTML.
|
|
172
30
|
*/
|
|
173
31
|
async function transformTabs(html) {
|
|
174
|
-
|
|
175
|
-
|
|
32
|
+
if (!/<tabs/i.test(html)) return html;
|
|
33
|
+
const result = (await require_napi.importNapiModule()).transformTabsEmbeds(html, tabGroupCounter);
|
|
34
|
+
tabGroupCounter += result.groupCount;
|
|
35
|
+
return result.html;
|
|
176
36
|
}
|
|
177
37
|
/**
|
|
178
38
|
* Generate dynamic CSS for :has() based tab switching.
|
package/dist/tabs.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabs.cjs","names":["
|
|
1
|
+
{"version":3,"file":"tabs.cjs","names":["importNapiModule"],"sources":["../src/plugins/tabs.ts"],"sourcesContent":["/**\n * Tabs Plugin - Pure CSS implementation\n *\n * Transforms <Tabs>/<Tab> components into accessible HTML with CSS :has()\n * based tab switching (no JavaScript required).\n *\n * The HTML rewrite is performed in Rust (`transformTabsEmbeds` in\n * @ox-content/napi), replacing the previous rehype parse/stringify round-trip.\n * The per-build group counter (consumed by `generateTabsCSS`) stays here and is\n * advanced by the number of groups the Rust transform reports, so CSS\n * generation still covers exactly the groups that were emitted.\n */\n\nimport { importNapiModule } from \"../napi\";\n\nlet tabGroupCounter = 0;\n\n/**\n * Reset tab group counter (for testing and per dev-server request).\n */\nexport function resetTabGroupCounter(): void {\n tabGroupCounter = 0;\n}\n\n/**\n * Transform Tabs components in HTML.\n */\nexport async function transformTabs(html: string): Promise<string> {\n // Cheap marker check: skip the NAPI call entirely when there's no `<tabs>`\n // element. The Rust side guards the same way, but short-circuiting here\n // avoids marshalling the whole document across the boundary.\n if (!/<tabs/i.test(html)) {\n return html;\n }\n\n const mod = await importNapiModule();\n const result = mod.transformTabsEmbeds(html, tabGroupCounter);\n tabGroupCounter += result.groupCount;\n return result.html;\n}\n\n/**\n * Generate dynamic CSS for :has() based tab switching.\n * This is needed because :has() selectors need unique IDs.\n */\nexport function generateTabsCSS(groupCount: number): string {\n if (groupCount === 0) return \"\";\n\n let css = \"/* Dynamic Tabs CSS */\\n\";\n\n for (let g = 0; g < groupCount; g++) {\n for (let t = 0; t < 8; t++) {\n css += `.ox-tabs[data-group=\"${g}\"]:has(#ox-tab-${g}-${t}:checked) .ox-tab-panel[data-tab=\"${t}\"] { display: block; }\\n`;\n }\n }\n\n return css;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAeA,IAAI,kBAAkB;;;;AAKtB,SAAgB,uBAA6B;CAC3C,kBAAkB;;;;;AAMpB,eAAsB,cAAc,MAA+B;CAIjE,IAAI,CAAC,SAAS,KAAK,KAAK,EACtB,OAAO;CAIT,MAAM,UAAS,MADGA,aAAAA,kBAAkB,EACjB,oBAAoB,MAAM,gBAAgB;CAC7D,mBAAmB,OAAO;CAC1B,OAAO,OAAO;;;;;;AAOhB,SAAgB,gBAAgB,YAA4B;CAC1D,IAAI,eAAe,GAAG,OAAO;CAE7B,IAAI,MAAM;CAEV,KAAK,IAAI,IAAI,GAAG,IAAI,YAAY,KAC9B,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KACrB,OAAO,wBAAwB,EAAE,iBAAiB,EAAE,GAAG,EAAE,oCAAoC,EAAE;CAInG,OAAO"}
|
package/dist/tabs2.mjs
CHANGED
|
@@ -1,170 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import rehypeParse from "rehype-parse";
|
|
3
|
-
import rehypeStringify from "rehype-stringify";
|
|
1
|
+
import { t as importNapiModule } from "./napi.mjs";
|
|
4
2
|
//#region src/plugins/tabs.ts
|
|
5
3
|
/**
|
|
6
4
|
* Tabs Plugin - Pure CSS implementation
|
|
7
5
|
*
|
|
8
|
-
* Transforms <Tabs>/<Tab> components into accessible HTML
|
|
9
|
-
*
|
|
6
|
+
* Transforms <Tabs>/<Tab> components into accessible HTML with CSS :has()
|
|
7
|
+
* based tab switching (no JavaScript required).
|
|
8
|
+
*
|
|
9
|
+
* The HTML rewrite is performed in Rust (`transformTabsEmbeds` in
|
|
10
|
+
* @ox-content/napi), replacing the previous rehype parse/stringify round-trip.
|
|
11
|
+
* The per-build group counter (consumed by `generateTabsCSS`) stays here and is
|
|
12
|
+
* advanced by the number of groups the Rust transform reports, so CSS
|
|
13
|
+
* generation still covers exactly the groups that were emitted.
|
|
10
14
|
*/
|
|
11
15
|
let tabGroupCounter = 0;
|
|
12
16
|
/**
|
|
13
|
-
* Reset tab group counter (for testing).
|
|
17
|
+
* Reset tab group counter (for testing and per dev-server request).
|
|
14
18
|
*/
|
|
15
19
|
function resetTabGroupCounter() {
|
|
16
20
|
tabGroupCounter = 0;
|
|
17
21
|
}
|
|
18
22
|
/**
|
|
19
|
-
* Get element attribute value.
|
|
20
|
-
*/
|
|
21
|
-
function getAttribute(el, name) {
|
|
22
|
-
const value = el.properties?.[name];
|
|
23
|
-
if (typeof value === "string") return value;
|
|
24
|
-
if (Array.isArray(value)) return value.join(" ");
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Parse Tab elements from Tabs children.
|
|
28
|
-
*/
|
|
29
|
-
function parseTabChildren(children) {
|
|
30
|
-
const tabs = [];
|
|
31
|
-
for (const child of children) {
|
|
32
|
-
if (child.type !== "element") continue;
|
|
33
|
-
if (child.tagName.toLowerCase() === "tab") {
|
|
34
|
-
const label = getAttribute(child, "label") || `Tab ${tabs.length + 1}`;
|
|
35
|
-
tabs.push({
|
|
36
|
-
label,
|
|
37
|
-
content: child.children.filter((c) => c.type === "element" || c.type === "text")
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return tabs;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Create the HTML structure for tabs.
|
|
45
|
-
*/
|
|
46
|
-
function createTabsElement(tabs, groupId) {
|
|
47
|
-
const children = [];
|
|
48
|
-
const headerChildren = [];
|
|
49
|
-
tabs.forEach((tab, index) => {
|
|
50
|
-
const inputId = `ox-tab-${groupId}-${index}`;
|
|
51
|
-
headerChildren.push({
|
|
52
|
-
type: "element",
|
|
53
|
-
tagName: "input",
|
|
54
|
-
properties: {
|
|
55
|
-
type: "radio",
|
|
56
|
-
name: `ox-tabs-${groupId}`,
|
|
57
|
-
id: inputId,
|
|
58
|
-
checked: index === 0 ? true : void 0
|
|
59
|
-
},
|
|
60
|
-
children: []
|
|
61
|
-
});
|
|
62
|
-
headerChildren.push({
|
|
63
|
-
type: "element",
|
|
64
|
-
tagName: "label",
|
|
65
|
-
properties: { htmlFor: inputId },
|
|
66
|
-
children: [{
|
|
67
|
-
type: "text",
|
|
68
|
-
value: tab.label
|
|
69
|
-
}]
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
children.push({
|
|
73
|
-
type: "element",
|
|
74
|
-
tagName: "div",
|
|
75
|
-
properties: { className: ["ox-tabs-header"] },
|
|
76
|
-
children: headerChildren
|
|
77
|
-
});
|
|
78
|
-
tabs.forEach((tab, index) => {
|
|
79
|
-
children.push({
|
|
80
|
-
type: "element",
|
|
81
|
-
tagName: "div",
|
|
82
|
-
properties: {
|
|
83
|
-
className: ["ox-tab-panel"],
|
|
84
|
-
"data-tab": String(index)
|
|
85
|
-
},
|
|
86
|
-
children: tab.content
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
return {
|
|
90
|
-
type: "element",
|
|
91
|
-
tagName: "div",
|
|
92
|
-
properties: {
|
|
93
|
-
className: ["ox-tabs"],
|
|
94
|
-
"data-group": groupId
|
|
95
|
-
},
|
|
96
|
-
children
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Create fallback HTML using <details> elements.
|
|
101
|
-
*/
|
|
102
|
-
function createFallbackElement(tabs) {
|
|
103
|
-
const children = [];
|
|
104
|
-
tabs.forEach((tab, index) => {
|
|
105
|
-
children.push({
|
|
106
|
-
type: "element",
|
|
107
|
-
tagName: "details",
|
|
108
|
-
properties: { open: index === 0 ? true : void 0 },
|
|
109
|
-
children: [{
|
|
110
|
-
type: "element",
|
|
111
|
-
tagName: "summary",
|
|
112
|
-
properties: {},
|
|
113
|
-
children: [{
|
|
114
|
-
type: "text",
|
|
115
|
-
value: tab.label
|
|
116
|
-
}]
|
|
117
|
-
}, {
|
|
118
|
-
type: "element",
|
|
119
|
-
tagName: "div",
|
|
120
|
-
properties: { className: ["ox-tabs-fallback-content"] },
|
|
121
|
-
children: tab.content
|
|
122
|
-
}]
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
return {
|
|
126
|
-
type: "element",
|
|
127
|
-
tagName: "noscript",
|
|
128
|
-
properties: {},
|
|
129
|
-
children: [{
|
|
130
|
-
type: "element",
|
|
131
|
-
tagName: "div",
|
|
132
|
-
properties: { className: ["ox-tabs-fallback"] },
|
|
133
|
-
children
|
|
134
|
-
}]
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Rehype plugin to transform Tabs components.
|
|
139
|
-
*/
|
|
140
|
-
function rehypeTabs() {
|
|
141
|
-
return (tree) => {
|
|
142
|
-
const visit = (node) => {
|
|
143
|
-
if ("children" in node) for (let i = 0; i < node.children.length; i++) {
|
|
144
|
-
const child = node.children[i];
|
|
145
|
-
if (child.type === "element") if (child.tagName.toLowerCase() === "tabs") {
|
|
146
|
-
const tabs = parseTabChildren(child.children);
|
|
147
|
-
if (tabs.length > 0) {
|
|
148
|
-
const wrapper = {
|
|
149
|
-
type: "element",
|
|
150
|
-
tagName: "div",
|
|
151
|
-
properties: { className: ["ox-tabs-container"] },
|
|
152
|
-
children: [createTabsElement(tabs, String(tabGroupCounter++)), createFallbackElement(tabs)]
|
|
153
|
-
};
|
|
154
|
-
node.children[i] = wrapper;
|
|
155
|
-
}
|
|
156
|
-
} else visit(child);
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
visit(tree);
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
23
|
* Transform Tabs components in HTML.
|
|
164
24
|
*/
|
|
165
25
|
async function transformTabs(html) {
|
|
166
|
-
|
|
167
|
-
|
|
26
|
+
if (!/<tabs/i.test(html)) return html;
|
|
27
|
+
const result = (await importNapiModule()).transformTabsEmbeds(html, tabGroupCounter);
|
|
28
|
+
tabGroupCounter += result.groupCount;
|
|
29
|
+
return result.html;
|
|
168
30
|
}
|
|
169
31
|
/**
|
|
170
32
|
* Generate dynamic CSS for :has() based tab switching.
|
package/dist/tabs2.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabs2.mjs","names":[],"sources":["../src/plugins/tabs.ts"],"sourcesContent":["/**\n * Tabs Plugin - Pure CSS implementation\n *\n * Transforms <Tabs>/<Tab> components into accessible HTML
|
|
1
|
+
{"version":3,"file":"tabs2.mjs","names":[],"sources":["../src/plugins/tabs.ts"],"sourcesContent":["/**\n * Tabs Plugin - Pure CSS implementation\n *\n * Transforms <Tabs>/<Tab> components into accessible HTML with CSS :has()\n * based tab switching (no JavaScript required).\n *\n * The HTML rewrite is performed in Rust (`transformTabsEmbeds` in\n * @ox-content/napi), replacing the previous rehype parse/stringify round-trip.\n * The per-build group counter (consumed by `generateTabsCSS`) stays here and is\n * advanced by the number of groups the Rust transform reports, so CSS\n * generation still covers exactly the groups that were emitted.\n */\n\nimport { importNapiModule } from \"../napi\";\n\nlet tabGroupCounter = 0;\n\n/**\n * Reset tab group counter (for testing and per dev-server request).\n */\nexport function resetTabGroupCounter(): void {\n tabGroupCounter = 0;\n}\n\n/**\n * Transform Tabs components in HTML.\n */\nexport async function transformTabs(html: string): Promise<string> {\n // Cheap marker check: skip the NAPI call entirely when there's no `<tabs>`\n // element. The Rust side guards the same way, but short-circuiting here\n // avoids marshalling the whole document across the boundary.\n if (!/<tabs/i.test(html)) {\n return html;\n }\n\n const mod = await importNapiModule();\n const result = mod.transformTabsEmbeds(html, tabGroupCounter);\n tabGroupCounter += result.groupCount;\n return result.html;\n}\n\n/**\n * Generate dynamic CSS for :has() based tab switching.\n * This is needed because :has() selectors need unique IDs.\n */\nexport function generateTabsCSS(groupCount: number): string {\n if (groupCount === 0) return \"\";\n\n let css = \"/* Dynamic Tabs CSS */\\n\";\n\n for (let g = 0; g < groupCount; g++) {\n for (let t = 0; t < 8; t++) {\n css += `.ox-tabs[data-group=\"${g}\"]:has(#ox-tab-${g}-${t}:checked) .ox-tab-panel[data-tab=\"${t}\"] { display: block; }\\n`;\n }\n }\n\n return css;\n}\n"],"mappings":";;;;;;;;;;;;;;AAeA,IAAI,kBAAkB;;;;AAKtB,SAAgB,uBAA6B;CAC3C,kBAAkB;;;;;AAMpB,eAAsB,cAAc,MAA+B;CAIjE,IAAI,CAAC,SAAS,KAAK,KAAK,EACtB,OAAO;CAIT,MAAM,UAAS,MADG,kBAAkB,EACjB,oBAAoB,MAAM,gBAAgB;CAC7D,mBAAmB,OAAO;CAC1B,OAAO,OAAO;;;;;;AAOhB,SAAgB,gBAAgB,YAA4B;CAC1D,IAAI,eAAe,GAAG,OAAO;CAE7B,IAAI,MAAM;CAEV,KAAK,IAAI,IAAI,GAAG,IAAI,YAAY,KAC9B,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KACrB,OAAO,wBAAwB,EAAE,iBAAiB,EAAE,GAAG,EAAE,oCAAoC,EAAE;CAInG,OAAO"}
|
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", {
|