---
type PageItem = {
url: string;
title: string;
};
type Section = {
title: string;
items: PageItem[];
};
async function getSectionPages(sectionPath: string): Promise<PageItem[]> {
const allPages = await Astro.glob("../pages/**/*.mdx");
return allPages
.filter((page) => page.file.includes(`/pages/${sectionPath}/`))
.map((page) => {
const fileName = page.file.split("/").pop()?.replace(".mdx", "") || "";
const url = `/${sectionPath}/${fileName}`;
const title =
page.frontmatter?.title ||
fileName.replace(/^\d+-/, "").replace(/-/g, " ");
return { url, title, order: parseInt(fileName) || 999 };
})
.sort((a, b) => a.order - b.order);
}
const currentPath = Astro.url.pathname;
const sections: Section[] = [];
const sectionPaths = ["html", "tips"];
for (const path of sectionPaths) {
const pages = await getSectionPages(path);
if (pages.length > 0) {
sections.push({
title: path.charAt(0).toUpperCase() + path.slice(1),
items: pages,
});
}
}
---
<nav class="toc">
<ul class="toc-list">
<li class="toc-item">
<a href="/" class="toc-link">Introduction</a>
</li>
{sections.map(section => {
const isActive = section.items.some(item => currentPath.startsWith(item.url));
return (
<li class="toc-item">
<details open={isActive}>
<summary class="toc-section-title">{section.title}</summary>
<ul class="toc-sublist">
{section.items.map(item => (
<li class="toc-subitem">
<a href={item.url} class="toc-link">{item.title}</a>
</li>
))}
</ul>
</details>
</li>
);
})}
</ul>
</nav>
<style>
.toc {
font-size: 14px;
}
.toc-list {
list-style: none;
padding: 0;
margin: 0;
}
.toc-item {
margin: 0;
}
.toc-link {
display: block;
padding: 4px 8px;
color: #0969da;
text-decoration: none;
border-radius: 6px;
}
.toc-link:hover {
background-color: #f6f8fa;
text-decoration: none;
}
details {
margin: 4px 0;
}
summary {
cursor: pointer;
padding: 4px 8px;
font-weight: 600;
color: #1f2328;
border-radius: 6px;
list-style: none;
}
summary::-webkit-details-marker {
display: none;
}
summary::before {
content: '▶';
display: inline-block;
margin-right: 6px;
font-size: 10px;
transition: transform 0.2s;
}
details[open] summary::before {
transform: rotate(90deg);
}
summary:hover {
background-color: #f6f8fa;
}
.toc-sublist {
list-style: none;
padding-left: 16px;
margin: 4px 0;
}
.toc-subitem {
margin: 0;
}
@media (prefers-color-scheme: dark) {
.toc-link {
color: #4493f8;
}
.toc-link:hover {
background-color: #161b22;
}
summary {
color: #e6edf3;
}
summary:hover {
background-color: #161b22;
}
}
</style>