Skip to content

Commit 51abd2a

Browse files
committed
blog formatting
1 parent 3ac57a8 commit 51abd2a

File tree

1 file changed

+87
-19
lines changed

1 file changed

+87
-19
lines changed

src/routes/_libraries/blog.$.tsx

Lines changed: 87 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@ import { setResponseHeaders } from '@tanstack/react-start/server'
1414
import { allPosts } from 'content-collections'
1515
import * as React from 'react'
1616
import { MarkdownContent } from '~/components/MarkdownContent'
17-
import { GamFooter, GamHeader } from '~/components/Gam'
17+
import { GamHeader } from '~/components/Gam'
1818
import { AdGate } from '~/contexts/AdsContext'
1919
import { ArrowLeft } from 'lucide-react'
20+
import { Toc } from '~/components/Toc'
21+
import { TocMobile } from '~/components/TocMobile'
22+
import { renderMarkdown } from '~/utils/markdown'
2023

2124
function 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

Comments
 (0)