@@ -18,15 +18,29 @@ type PageProps = Readonly<{
1818 searchParams : Promise < { version ?: string } >
1919} >
2020
21+ function resolvePath ( baseFile : string , relativePath : string ) {
22+ if ( relativePath . startsWith ( '/' ) ) return relativePath . slice ( 1 ) ;
23+ const stack = baseFile . split ( '/' ) ;
24+ stack . pop ( ) ; // Remove current filename
25+ const parts = relativePath . split ( '/' ) ;
26+ for ( const part of parts ) {
27+ if ( part === '.' ) continue ;
28+ if ( part === '..' ) {
29+ if ( stack . length > 0 ) stack . pop ( ) ;
30+ } else {
31+ stack . push ( part ) ;
32+ }
33+ }
34+ return stack . join ( '/' ) ;
35+ }
36+
2137export default async function Page ( props : PageProps ) {
2238 const params = await props . params
2339 const searchParams = await props . searchParams
2440
25- // Get version from URL or use default
2641 const version = ( searchParams . version as VersionKey ) || getDefaultVersion ( )
2742 const branch = getBranchForVersion ( version )
2843
29- // Build page map for this branch
3044 const { routeMap, filePaths } = await buildPageMapForBranch ( branch )
3145
3246 const route = params . slug ? params . slug . join ( '/' ) : ''
@@ -42,10 +56,84 @@ export default async function Page(props: PageProps) {
4256 `https://raw.githubusercontent.com/${ user } /${ repo } /${ branch } /${ docsPath } ${ filePath } ` ,
4357 { headers : makeGitHubHeaders ( ) , cache : 'no-store' }
4458 )
59+
4560 if ( ! response . ok ) notFound ( )
4661
47- const data = await response . text ( )
48- const processedData = convertHtmlScriptsToJsxComments ( data )
62+ const rawText = await response . text ( )
63+
64+ let contentWithIncludes = rawText ;
65+ const includeRegex = / { % \s * i n c l u d e \s + [ " ' ] ( [ ^ " ' ] + ) [ " ' ] \s * % } / g;
66+ const includeMatches = Array . from ( rawText . matchAll ( includeRegex ) ) ;
67+
68+ if ( includeMatches . length > 0 ) {
69+ const uniqueIncludes = [ ...new Set ( includeMatches . map ( m => m [ 1 ] ) ) ] ;
70+
71+ const includeContents = await Promise . all ( uniqueIncludes . map ( async ( relativePath ) => {
72+ const resolvedPath = resolvePath ( filePath , relativePath ) ;
73+ const url = `https://raw.githubusercontent.com/${ user } /${ repo } /${ branch } /${ docsPath } ${ resolvedPath } ` ;
74+
75+ try {
76+ const res = await fetch ( url , { headers : makeGitHubHeaders ( ) , cache : 'no-store' } ) ;
77+ if ( res . ok ) {
78+ return { path : relativePath , text : await res . text ( ) } ;
79+ }
80+
81+ const rootUrl = `https://raw.githubusercontent.com/${ user } /${ repo } /${ branch } /${ resolvedPath } ` ;
82+ const rootRes = await fetch ( rootUrl , { headers : makeGitHubHeaders ( ) , cache : 'no-store' } ) ;
83+ if ( rootRes . ok ) {
84+ return { path : relativePath , text : await rootRes . text ( ) } ;
85+ }
86+
87+ return { path : relativePath , text : `> **Error**: Could not include \`${ relativePath } \` (File not found)` } ;
88+ } catch {
89+ return { path : relativePath , text : `> **Error**: Failed to fetch \`${ relativePath } \`` } ;
90+ }
91+ } ) ) ;
92+
93+ includeContents . forEach ( ( { path, text } ) => {
94+ const escapedPath = path . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ;
95+ const pattern = new RegExp ( `{%\\s*include\\s+["']${ escapedPath } ["']\\s*%}` , 'g' ) ;
96+ contentWithIncludes = contentWithIncludes . replace ( pattern , ( ) => text ) ;
97+ } ) ;
98+ }
99+
100+ const filePathToRoute = new Map < string , string > ( ) ;
101+ Object . entries ( routeMap ) . forEach ( ( [ r , fp ] ) => filePathToRoute . set ( fp , r ) ) ;
102+
103+ let rewrittenText = contentWithIncludes . replace ( / ( ! ? \[ .* ?\] ) \( ( .* ?) \) / g, ( match , label , link ) => {
104+ if ( / ^ ( h t t p | h t t p s | m a i l t o : | # ) / . test ( link ) ) return match ;
105+
106+ const isImage = label . startsWith ( '!' ) ;
107+ const [ linkUrl , linkHash ] = link . split ( '#' ) ;
108+
109+ const resolvedPath = resolvePath ( filePath , linkUrl ) ;
110+
111+ if ( isImage ) {
112+ const rawUrl = `https://raw.githubusercontent.com/${ user } /${ repo } /${ branch } /${ docsPath } ${ resolvedPath } ` ;
113+ return `${ label } (${ rawUrl } )` ;
114+ } else {
115+ let targetRoute = filePathToRoute . get ( resolvedPath ) ;
116+ if ( ! targetRoute ) targetRoute = filePathToRoute . get ( resolvedPath + '.md' ) ;
117+ if ( ! targetRoute ) targetRoute = filePathToRoute . get ( resolvedPath + '.mdx' ) ;
118+
119+ if ( targetRoute ) {
120+ return `${ label } (/docs/${ targetRoute } ${ linkHash ? '#' + linkHash : '' } )` ;
121+ }
122+
123+ return `${ label } (https://raw.githubusercontent.com/${ user } /${ repo } /${ branch } /${ docsPath } ${ resolvedPath } )` ;
124+ }
125+ } ) ;
126+
127+ rewrittenText = rewrittenText . replace ( / < i m g \s + ( [ ^ > ] * ?) s r c = [ " ' ] ( [ ^ " ' ] + ) [ " ' ] ( [ ^ > ] * ?) > / gi, ( match , pre , src , post ) => {
128+ if ( / ^ ( h t t p | h t t p s | m a i l t o : | # | d a t a : ) / . test ( src ) ) return match ;
129+
130+ const resolvedPath = resolvePath ( filePath , src ) ;
131+ const rawUrl = `https://raw.githubusercontent.com/${ user } /${ repo } /${ branch } /${ docsPath } ${ resolvedPath } ` ;
132+
133+ return `<img ${ pre } src="${ rawUrl } "${ post } >` ;
134+ } ) ;
135+
136+ const processedData = convertHtmlScriptsToJsxComments ( rewrittenText )
49137 . replace ( / < b r \s * \/ ? > / gi, '<br />' )
50138 . replace ( / a l i g n = c e n t e r / g, 'align="center"' )
51139 . replace ( / f r a m e b o r d e r = " 0 " / g, 'frameBorder="0"' )
0 commit comments