Files
supabase/apps/docs/components/GuidesTableOfContents.tsx
Charis ec86bbc4fe feat: new auth ia (#22812)
Co-authored-by: Kang Ming <kang.ming1996@gmail.com>
Co-authored-by: Joel Lee <lee.yi.jie.joel@gmail.com>
2024-05-07 20:15:06 +00:00

143 lines
4.3 KiB
TypeScript

import { usePathname } from 'next/navigation'
import { useEffect, useState } from 'react'
import { cn } from 'ui'
import { ExpandableVideo } from 'ui-patterns/ExpandableVideo'
import { proxy, useSnapshot } from 'valtio'
import {
highlightSelectedTocItem,
removeAnchor,
} from '~/components/CustomHTMLElements/CustomHTMLElements.utils'
import { Feedback } from '~/components/Feedback'
import useHash from '~/hooks/useHash'
const formatSlug = (slug: string) => {
// [Joshen] We will still provide support for headers declared like this:
// ## REST API {#rest-api-overview}
// At least for now, this was a docusaurus thing.
if (slug.includes('#')) return slug.split('#')[1]
return slug
}
const formatTOCHeader = (content: string) => {
let begin = false
const res = []
for (const x of content) {
if (x === '`') {
if (!begin) {
begin = true
res.push(`<code class="text-xs border rounded bg-muted">`)
} else {
begin = false
res.push(`</code>`)
}
} else {
res.push(x)
}
}
return res.join('')
}
const tocRenderSwitch = proxy({
renderFlag: 0,
toggleRenderFlag: () => void (tocRenderSwitch.renderFlag = (tocRenderSwitch.renderFlag + 1) % 2),
})
const useSubscribeTocRerender = () => {
const { renderFlag } = useSnapshot(tocRenderSwitch)
return void renderFlag // Prevent it from being detected as unused code
}
const useTocRerenderTrigger = () => {
const { toggleRenderFlag } = useSnapshot(tocRenderSwitch)
return toggleRenderFlag
}
const GuidesTableOfContents = ({
className,
overrideToc,
video,
}: {
className?: string
overrideToc?: Array<{ text: string; link: string; level: number }>
video?: string
}) => {
useSubscribeTocRerender()
const [tocList, setTocList] = useState([])
const pathname = usePathname()
const [hash] = useHash()
const displayedList = overrideToc ?? tocList
useEffect(() => {
if (overrideToc) return
/**
* Because we're directly querying the DOM, needs the setTimeout so the DOM
* update will happen first.
*/
const timeoutHandle = setTimeout(() => {
const headings = Array.from(
document.querySelector('#sb-docs-guide-main-article')?.querySelectorAll('h2, h3') ?? []
)
const newHeadings = headings
.filter((heading) => heading.id)
.map((heading) => {
const text = heading.textContent.replace('#', '')
const link = heading.querySelector('a')?.getAttribute('href')
if (!link) return null
const level = heading.tagName === 'H2' ? 2 : 3
return { text, link, level }
})
.filter(Boolean)
setTocList(newHeadings)
})
return () => clearTimeout(timeoutHandle)
/**
* window.location.href needed to recalculate toc when page changes,
* `useSubscribeTocRerender` above will trigger the rerender
*/
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [overrideToc, typeof window !== 'undefined' && window.location.href])
useEffect(() => {
if (hash && displayedList.length > 0) {
highlightSelectedTocItem(hash)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hash, JSON.stringify(displayedList)])
if (!displayedList.length) return
const tocVideoPreview = `http://img.youtube.com/vi/${video}/0.jpg`
return (
<div className={cn('border-l', 'thin-scrollbar overflow-y-auto', 'px-2', className)}>
{video && (
<div className="relative mb-6 pl-5">
<ExpandableVideo imgUrl={tocVideoPreview} videoId={video} />
</div>
)}
<Feedback key={pathname} />
<span className="block font-mono text-xs uppercase text-foreground px-5 mb-6">
On this page
</span>
<ul className="toc-menu list-none pl-5 text-[0.8rem] grid gap-2">
{displayedList.map((item, i) => (
<li key={`${item.level}-${i}`} className={item.level === 3 ? 'ml-4' : ''}>
<a
href={`#${formatSlug(item.link)}`}
className="text-foreground-lighter hover:text-brand-link transition-colors"
dangerouslySetInnerHTML={{ __html: formatTOCHeader(removeAnchor(item.text)) }}
/>
</li>
))}
</ul>
</div>
)
}
export default GuidesTableOfContents
export { useTocRerenderTrigger }