@@ -6,12 +6,140 @@ import * as runtime from 'react/jsx-runtime';
66import { remarkPlugins } from '../../plugins/markdownToHtml' ;
77import remarkGfm from 'remark-gfm' ;
88import remarkFrontmatter from 'remark-frontmatter' ;
9- import { prepareMDX } from './prepareMDX' ; // Assuming prepareMDX is modularized
109import { MDXComponents } from '../components/MDX/MDXComponents' ; // Assuming MDXComponents is modularized
1110import visit from 'unist-util-visit' ;
11+ import { u } from 'unist-builder' ;
1212
1313const DISK_CACHE_BREAKER = 11 ;
1414
15+ export function remarkTOCExtractor ( { maxDepth = Infinity } = { } ) {
16+ return ( tree , file ) => {
17+ const toc = [ ] ;
18+
19+ visit ( tree , ( node ) => {
20+ // Standard markdown headings
21+ if ( node . type === 'heading' ) {
22+ if ( node . depth > maxDepth ) {
23+ return ;
24+ }
25+ const text = node . children
26+ . filter ( ( child ) => child . type === 'text' )
27+ . map ( ( child ) => child . value )
28+ . join ( '' ) ;
29+ const id =
30+ node . data ?. hProperties ?. id || text . toLowerCase ( ) . replace ( / \s + / g, '-' ) ;
31+
32+ toc . push ( {
33+ depth : node . depth ,
34+ text,
35+ url : `#${ id } ` ,
36+ } ) ;
37+ }
38+
39+ // MDX custom components (e.g., <TeamMember>)
40+ else if ( node . type === 'mdxJsxFlowElement' ) {
41+ switch ( node . name ) {
42+ case 'TeamMember' : {
43+ // Extract attributes like name, permalink, etc.
44+ let name = 'Team Member' ;
45+ let permalink = 'team-member' ;
46+
47+ if ( Array . isArray ( node . attributes ) ) {
48+ for ( const attr of node . attributes ) {
49+ if ( attr . name === 'name' && attr . value ) {
50+ name = attr . value ;
51+ } else if ( attr . name === 'permalink' && attr . value ) {
52+ permalink = attr . value ;
53+ }
54+ }
55+ }
56+
57+ toc . push ( {
58+ url : `#${ permalink } ` ,
59+ depth : 3 ,
60+ text : name ,
61+ } ) ;
62+ break ;
63+ }
64+
65+ // Similarly handle <Challenges>, <Recap>, or any other custom tags if needed
66+ case 'Challenges' :
67+ toc . push ( {
68+ url : '#challenges' ,
69+ depth : 2 ,
70+ text : 'Challenges' ,
71+ } ) ;
72+ break ;
73+ case 'Recap' :
74+ toc . push ( {
75+ url : '#recap' ,
76+ depth : 2 ,
77+ text : 'Recap' ,
78+ } ) ;
79+ break ;
80+ default :
81+ break ;
82+ }
83+ }
84+ } ) ;
85+
86+ // Insert "Overview" at the top if there's at least one heading
87+ if ( toc . length > 0 ) {
88+ toc . unshift ( {
89+ url : '#' ,
90+ text : 'Overview' ,
91+ depth : 2 ,
92+ } ) ;
93+ }
94+
95+ file . data . toc = toc ;
96+ } ;
97+ }
98+
99+ function remarkWrapElements ( ) {
100+ const fullWidthTypes = [
101+ 'Sandpack' ,
102+ 'FullWidth' ,
103+ 'Illustration' ,
104+ 'IllustrationBlock' ,
105+ 'Challenges' ,
106+ 'Recipes' ,
107+ ] ;
108+
109+ return ( tree ) => {
110+ const newChildren = [ ] ;
111+ let wrapQueue = [ ] ;
112+
113+ function flushWrapper ( ) {
114+ if ( wrapQueue . length > 0 ) {
115+ newChildren . push (
116+ u ( 'mdxJsxFlowElement' , {
117+ name : 'MaxWidth' ,
118+ attributes : [ ] ,
119+ children : wrapQueue ,
120+ } )
121+ ) ;
122+ wrapQueue = [ ] ;
123+ }
124+ }
125+
126+ for ( const node of tree . children ) {
127+ if (
128+ node . type === 'mdxJsxFlowElement' &&
129+ fullWidthTypes . includes ( node . name )
130+ ) {
131+ flushWrapper ( ) ;
132+ newChildren . push ( node ) ;
133+ } else {
134+ wrapQueue . push ( node ) ;
135+ }
136+ }
137+ flushWrapper ( ) ;
138+
139+ tree . children = newChildren ;
140+ } ;
141+ }
142+
15143export default async function compileMDX (
16144 mdx : string ,
17145 path : string | string [ ] ,
@@ -44,46 +172,48 @@ export default async function compileMDX(
44172 }
45173
46174 // Compile the MDX source code
47- const code = String (
48- await compile ( mdx , {
49- remarkPlugins : [ ...remarkPlugins , remarkGfm , remarkFrontmatter ] ,
50-
51- rehypePlugins : [
52- // Support stuff like ```js App.js {1-5} active by passing it through.
53- function rehypeMetaAsAttributes ( ) {
54- return ( tree ) => {
55- visit ( tree , 'element' , ( node ) => {
56- if (
57- // @ts -expect-error -- tagName is a valid property
58- node . tagName === 'code' &&
59- node . data &&
60- node . data . meta
61- ) {
62- // @ts -expect-error -- properties is a valid property
63- node . properties . meta = node . data . meta ;
64- }
65- } ) ;
66- } ;
67- } ,
68- ] ,
69- outputFormat : 'function-body' ,
70- } )
71- ) ;
175+ const code = await compile ( mdx , {
176+ remarkPlugins : [
177+ ...remarkPlugins ,
178+ remarkGfm ,
179+ remarkFrontmatter ,
180+ remarkTOCExtractor ,
181+ remarkWrapElements ,
182+ ] ,
183+
184+ rehypePlugins : [
185+ // Support stuff like ```js App.js {1-5} active by passing it through.
186+ function rehypeMetaAsAttributes ( ) {
187+ return ( tree ) => {
188+ visit ( tree , 'element' , ( node ) => {
189+ if (
190+ // @ts -expect-error -- tagName is a valid property
191+ node . tagName === 'code' &&
192+ node . data &&
193+ node . data . meta
194+ ) {
195+ // @ts -expect-error -- properties is a valid property
196+ node . properties . meta = node . data . meta ;
197+ }
198+ } ) ;
199+ } ;
200+ } ,
201+ ] ,
202+ outputFormat : 'function-body' ,
203+ } ) ;
72204
73205 const { data : meta } = grayMatter ( mdx ) ;
74206
75- const { default : MDXContent } = await run ( code , {
207+ const { default : MDXContent } = await run ( String ( code ) , {
76208 ...runtime ,
77209 baseUrl : import . meta. url ,
78210 } ) ;
79211
80- const { toc, children} = prepareMDX (
81- < MDXContent components = { { ...MDXComponents } } />
82- ) ;
212+ const content = < MDXContent components = { { ...MDXComponents } } /> ;
83213
84214 return {
85- content : children ,
86- toc,
215+ content,
216+ toc : code . data . toc ,
87217 meta,
88218 } ;
89219}
0 commit comments