diff --git a/src/app/docs/[...slug]/page.tsx b/src/app/docs/[...slug]/page.tsx index 53ee320..63f943f 100644 --- a/src/app/docs/[...slug]/page.tsx +++ b/src/app/docs/[...slug]/page.tsx @@ -18,15 +18,29 @@ type PageProps = Readonly<{ searchParams: Promise<{ version?: string }> }> +function resolvePath(baseFile: string, relativePath: string) { + if (relativePath.startsWith('/')) return relativePath.slice(1); + const stack = baseFile.split('/'); + stack.pop(); // Remove current filename + const parts = relativePath.split('/'); + for (const part of parts) { + if (part === '.') continue; + if (part === '..') { + if (stack.length > 0) stack.pop(); + } else { + stack.push(part); + } + } + return stack.join('/'); +} + export default async function Page(props: PageProps) { const params = await props.params const searchParams = await props.searchParams - // Get version from URL or use default const version = (searchParams.version as VersionKey) || getDefaultVersion() const branch = getBranchForVersion(version) - // Build page map for this branch const { routeMap, filePaths } = await buildPageMapForBranch(branch) const route = params.slug ? params.slug.join('/') : '' @@ -42,10 +56,84 @@ export default async function Page(props: PageProps) { `https://raw.githubusercontent.com/${user}/${repo}/${branch}/${docsPath}${filePath}`, { headers: makeGitHubHeaders(), cache: 'no-store' } ) + if (!response.ok) notFound() - const data = await response.text() - const processedData = convertHtmlScriptsToJsxComments(data) + const rawText = await response.text() + + let contentWithIncludes = rawText; + const includeRegex = /{%\s*include\s+["']([^"']+)["']\s*%}/g; + const includeMatches = Array.from(rawText.matchAll(includeRegex)); + + if (includeMatches.length > 0) { + const uniqueIncludes = [...new Set(includeMatches.map(m => m[1]))]; + + const includeContents = await Promise.all(uniqueIncludes.map(async (relativePath) => { + const resolvedPath = resolvePath(filePath, relativePath); + const url = `https://raw.githubusercontent.com/${user}/${repo}/${branch}/${docsPath}${resolvedPath}`; + + try { + const res = await fetch(url, { headers: makeGitHubHeaders(), cache: 'no-store' }); + if (res.ok) { + return { path: relativePath, text: await res.text() }; + } + + const rootUrl = `https://raw.githubusercontent.com/${user}/${repo}/${branch}/${resolvedPath}`; + const rootRes = await fetch(rootUrl, { headers: makeGitHubHeaders(), cache: 'no-store' }); + if (rootRes.ok) { + return { path: relativePath, text: await rootRes.text() }; + } + + return { path: relativePath, text: `> **Error**: Could not include \`${relativePath}\` (File not found)` }; + } catch { + return { path: relativePath, text: `> **Error**: Failed to fetch \`${relativePath}\`` }; + } + })); + + includeContents.forEach(({ path, text }) => { + const escapedPath = path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const pattern = new RegExp(`{%\\s*include\\s+["']${escapedPath}["']\\s*%}`, 'g'); + contentWithIncludes = contentWithIncludes.replace(pattern, () => text); + }); + } + + const filePathToRoute = new Map(); + Object.entries(routeMap).forEach(([r, fp]) => filePathToRoute.set(fp, r)); + + let rewrittenText = contentWithIncludes.replace(/(!?\[.*?\])\((.*?)\)/g, (match, label, link) => { + if (/^(http|https|mailto:|#)/.test(link)) return match; + + const isImage = label.startsWith('!'); + const [linkUrl, linkHash] = link.split('#'); + + const resolvedPath = resolvePath(filePath, linkUrl); + + if (isImage) { + const rawUrl = `https://raw.githubusercontent.com/${user}/${repo}/${branch}/${docsPath}${resolvedPath}`; + return `${label}(${rawUrl})`; + } else { + let targetRoute = filePathToRoute.get(resolvedPath); + if (!targetRoute) targetRoute = filePathToRoute.get(resolvedPath + '.md'); + if (!targetRoute) targetRoute = filePathToRoute.get(resolvedPath + '.mdx'); + + if (targetRoute) { + return `${label}(/docs/${targetRoute}${linkHash ? '#' + linkHash : ''})`; + } + + return `${label}(https://raw.githubusercontent.com/${user}/${repo}/${branch}/${docsPath}${resolvedPath})`; + } + }); + + rewrittenText = rewrittenText.replace(/]*?)src=["']([^"']+)["']([^>]*?)>/gi, (match, pre, src, post) => { + if (/^(http|https|mailto:|#|data:)/.test(src)) return match; + + const resolvedPath = resolvePath(filePath, src); + const rawUrl = `https://raw.githubusercontent.com/${user}/${repo}/${branch}/${docsPath}${resolvedPath}`; + + return ``; + }); + + const processedData = convertHtmlScriptsToJsxComments(rewrittenText) .replace(//gi, '
') .replace(/align=center/g, 'align="center"') .replace(/frameborder="0"/g, 'frameBorder="0"')