@@ -14,9 +14,12 @@ import { setResponseHeaders } from '@tanstack/react-start/server'
1414import { allPosts } from 'content-collections'
1515import * as React from 'react'
1616import { MarkdownContent } from '~/components/MarkdownContent'
17- import { GamFooter , GamHeader } from '~/components/Gam'
17+ import { GamHeader } from '~/components/Gam'
1818import { AdGate } from '~/contexts/AdsContext'
1919import { ArrowLeft } from 'lucide-react'
20+ import { Toc } from '~/components/Toc'
21+ import { TocMobile } from '~/components/TocMobile'
22+ import { renderMarkdown } from '~/utils/markdown'
2023
2124function handleRedirects ( docsPath : string ) {
2225 if ( docsPath . includes ( 'directives-the-new-framework-lock-in' ) ) {
@@ -109,6 +112,58 @@ function BlogPost() {
109112
110113${ content } `
111114
115+ const { headings, markup } = React . useMemo (
116+ ( ) => renderMarkdown ( blogContent ) ,
117+ [ blogContent ] ,
118+ )
119+
120+ const isTocVisible = headings . length > 1
121+
122+ const markdownContainerRef = React . useRef < HTMLDivElement > ( null )
123+ const [ activeHeadings , setActiveHeadings ] = React . useState < Array < string > > ( [ ] )
124+
125+ const headingElementRefs = React . useRef <
126+ Record < string , IntersectionObserverEntry >
127+ > ( { } )
128+
129+ React . useEffect ( ( ) => {
130+ const callback = ( headingsList : Array < IntersectionObserverEntry > ) => {
131+ headingElementRefs . current = headingsList . reduce (
132+ ( map , headingElement ) => {
133+ map [ headingElement . target . id ] = headingElement
134+ return map
135+ } ,
136+ headingElementRefs . current ,
137+ )
138+
139+ const visibleHeadings : Array < IntersectionObserverEntry > = [ ]
140+ Object . keys ( headingElementRefs . current ) . forEach ( ( key ) => {
141+ const headingElement = headingElementRefs . current [ key ]
142+ if ( headingElement . isIntersecting ) {
143+ visibleHeadings . push ( headingElement )
144+ }
145+ } )
146+
147+ if ( visibleHeadings . length >= 1 ) {
148+ setActiveHeadings ( visibleHeadings . map ( ( h ) => h . target . id ) )
149+ }
150+ }
151+
152+ const observer = new IntersectionObserver ( callback , {
153+ rootMargin : '0px' ,
154+ threshold : 0.2 ,
155+ } )
156+
157+ const headingElements = Array . from (
158+ markdownContainerRef . current ?. querySelectorAll (
159+ 'h2[id], h3[id], h4[id], h5[id], h6[id]' ,
160+ ) ?? [ ] ,
161+ )
162+ headingElements . forEach ( ( el ) => observer . observe ( el ) )
163+
164+ return ( ) => observer . disconnect ( )
165+ } , [ headings ] )
166+
112167 const repo = 'tanstack/tanstack.com'
113168 const branch = 'main'
114169
@@ -131,39 +186,52 @@ ${content}`
131186 </ div >
132187 </ AdGate >
133188 < div className = "px-4" >
134- < div className = "w-full max-w-[900px] mx-auto" >
135- < div className = "mt-4 mb-2 md:mb-6 lg:mb-8" >
136- < Link
137- from = "/blog/$"
138- to = "/blog"
139- className = "font-black inline-flex items-center gap-2 p-1"
140- >
141- < ArrowLeft />
142- Back to Blog
143- </ Link >
144- </ div >
145- </ div >
146- < div className = "w-full max-w-[900px] mx-auto" >
189+ < div className = "w-full max-w-[1100px] mx-auto" >
147190 < div className = "flex-1 min-h-0 flex flex-col" >
148- < div className = "w-full flex bg-white/70 dark:bg-black/40 rounded-xl" >
149- < div className = "flex overflow-auto flex-col w-full p-2 lg:p-4 xl:p-6" >
191+ < div className = "w-full flex justify-center" >
192+ < div className = "w-full max-w-[700px] p-2 lg:p-4 xl:p-6" >
193+ < div className = "mt-2 mb-2" >
194+ < Link
195+ from = "/blog/$"
196+ to = "/blog"
197+ className = "font-black inline-flex items-center gap-2 p-1"
198+ >
199+ < ArrowLeft />
200+ Back to Blog
201+ </ Link >
202+ </ div >
203+ </ div >
204+ < div className = "max-w-32 md:max-w-36 xl:max-w-44 2xl:max-w-56 w-full hidden md:block" />
205+ </ div >
206+ { isTocVisible && < TocMobile headings = { headings } /> }
207+ < div className = "w-full flex justify-center" >
208+ < div className = "flex overflow-auto flex-col w-full max-w-[700px] p-2 lg:p-4 xl:p-6 pt-0" >
150209 < MarkdownContent
151210 title = { title }
152- rawContent = { blogContent }
211+ htmlMarkup = { markup }
153212 repo = { repo }
154213 branch = { branch }
155214 filePath = { filePath }
215+ containerRef = { markdownContainerRef }
156216 />
157217 </ div >
218+ { isTocVisible && (
219+ < div className = "pl-2 xl:pl-6 2xl:pl-8 max-w-32 md:max-w-36 xl:max-w-44 2xl:max-w-56 w-full hidden md:block py-4" >
220+ < Toc
221+ headings = { headings }
222+ activeHeadings = { activeHeadings }
223+ />
224+ </ div >
225+ ) }
158226 </ div >
159227 </ div >
160228 </ div >
161229 </ div >
162230 </ div >
163231 </ div >
164232 < AdGate >
165- < div className = "mb-8 ! py-0! mx-auto max-w-full overflow-x-hidden flex justify-center " >
166- < GamFooter />
233+ < div className = "py-2 pb-4 lg: py-4 lg:pb-6 xl:py-6 xl:pb-8 max-w-full " >
234+ < GamHeader />
167235 </ div >
168236 </ AdGate >
169237 </ div >
0 commit comments