website/src/layouts/Docs.astro
kolaente df67a946aa
All checks were successful
continuous-integration/drone/push Build is passing
fix: menu item in dark mode
2025-01-09 14:41:25 +01:00

159 lines
5.2 KiB
Plaintext

---
import {type MarkdownHeading} from 'astro'
import {getCollection} from 'astro:content'
import Layout from './Layout.astro'
import Prose from '../components/Prose.astro'
import TableOfContentsHeading from '../components/TableOfContentsHeading.astro'
interface Props {
title: string
headings: MarkdownHeading[]
}
interface MarkdownWithSubheadings extends MarkdownHeading {
subheadings: MarkdownWithSubheadings[]
}
const {title: originalTitle, headings} = Astro.props
// @ts-ignore: Property exists
const title = originalTitle ?? Astro.props.frontmatter?.title
// @ts-ignore: Property exists
const description = Astro.props?.frontmatter?.description || Astro.props.description
// Taken from https://kld.dev/building-table-of-contents/
function buildToc(headings: MarkdownHeading[]) {
const toc: MarkdownWithSubheadings[] = []
const parentHeadings = new Map()
headings.forEach((h) => {
const heading = {...h, subheadings: []}
parentHeadings.set(heading.depth, heading)
if (heading.depth === 2) {
toc.push(heading)
} else {
parentHeadings.get(heading.depth - 1)?.subheadings.push(heading)
}
})
return toc
}
const toc = buildToc(headings)
interface SectionItem {
url: string;
title: string;
}
type DocumentationArray = [string, SectionItem[]][];
async function getSidebarNavigation(): Promise<DocumentationArray> {
const docs = await getCollection('docs')
const entries: { [key: string]: SectionItem[] } = {}
docs.forEach(d => {
const groupKey = d.id.split('/')[0]
if (!entries[groupKey]) {
entries[groupKey] = []
}
if (d.data.hideInMenu) {
return
}
entries[groupKey].push({
url: '/docs/' + d.slug,
title: d.data.title,
})
})
const order = ['setup', 'usage', 'development']
const sortedData = order.reduce((obj: { [key: string]: SectionItem[] }, key) => {
if (entries[key]) {
obj[key] = entries[key].sort((a: SectionItem, b: SectionItem) => a.title.localeCompare(b.title))
}
return obj
}, {})
return Object.entries(sortedData)
}
const sidebarEntries = await getSidebarNavigation()
---
<Layout title={title} description={description}>
<div class="mx-auto flex max-w-screen-2xl flex-col lg:flex-row lg:pt-8">
<button
class="navbar-burger m-2 mt-6 block lg:hidden rounded-sm bg-gray-200 dark:bg-gray-800 py-2 px-1 text-center"
data-target="docsMenu"
>
Toggle Menu
</button>
<aside class="w-full lg:w-1/5 hidden lg:block order-1 px-2 text-gray-600 dark:text-gray-300" id="docsMenu">
<nav class="pb-4">
{sidebarEntries.map(([key, entries]) => (
<p class="pb-2 pt-4 pl-2 font-display uppercase font-bold text-gray-500 dark:text-gray-400">
{key}
</p>
<ul>
{entries.map(item => (
<li>
<a
href={item.url}
class={'block rounded px-2 py-1.5 hover:bg-gray-200 dark:hover:bg-gray-800 transition ' + ((Astro.url.pathname === item.url || Astro.url.pathname === item.url + '/') && ' !bg-gray-100 dark:!bg-gray-800 text-primary')}
>
{item.title}
</a>
</li>
))}
</ul>
))}
</nav>
</aside>
<main class="w-full lg:w-3/5 p-4 pt-6 lg:pt-2 order-3 lg:order-2" id="main-content">
<Prose>
<h1>{title}</h1>
<slot/>
</Prose>
</main>
<div class="w-full lg:w-1/5 pl-4 order-2 lg:order-3">
{toc.length > 0 &&
<div class="sticky top-2">
<div class="font-display font-bold">
On this page
</div>
<nav class="text-gray-600 dark:text-gray-300 pt-2 max-h-[calc(100vh-2rem)] overflow-y-auto overflow-x-hidden"
id="toc">
<ul>
{toc.map((heading) =>
<TableOfContentsHeading heading={heading}/>)}
</ul>
</nav>
</div>
}
</div>
</div>
</Layout>
<script>
// Adjusted from https://stackoverflow.com/a/73255559
const anchors = document.querySelectorAll('#main-content h2, #main-content h3, #main-content h4, #main-content h5, #main-content h6')
window.addEventListener('scroll', function () {
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
// highlight the last scrolled-to: set everything inactive first
anchors.forEach(anchor => {
const link = document.querySelector(`nav#toc ul li a[href="#${anchor.id}"]`)
if (link) link.classList.remove('text-primary')
})
// then iterate backwards, on the first match highlight it and break
for (let i = anchors.length - 1; i >= 0; i--) {
// @ts-ignore
if (scrollTop > anchors[i].offsetTop - 75) {
const link = document.querySelector(`nav ul li a[href="#${anchors[i].id}"]`)
if (link) link.classList.add('text-primary')
break
}
}
})
</script>