feat(组件): 实现可递归的TOC列表组件并重构目录结构

- 新增TOCList.astro组件用于递归渲染多级目录
- 重构TOC.astro组件逻辑,使用buildToc函数构建树形结构
- 移除原有平铺式目录渲染方式,改为组件化递归实现
This commit is contained in:
二叉树树
2026-01-05 16:21:44 +08:00
parent 98717f9d19
commit c0d5197717
2 changed files with 101 additions and 25 deletions

View File

@@ -1,6 +1,7 @@
---
import type { MarkdownHeading } from "astro";
import { siteConfig } from "../../config";
import TOCList from "./TOCList.astro";
interface Props {
class?: string;
@@ -27,34 +28,37 @@ const removeTailingHash = (text: string) => {
return text.substring(0, lastIndexOfHash);
};
let heading1Count = 1;
interface TocItem extends MarkdownHeading {
children: TocItem[];
}
const maxLevel = siteConfig.toc.depth;
function buildToc(headings: MarkdownHeading[]): TocItem[] {
const toc: TocItem[] = [];
const parentStack: TocItem[] = [];
headings.forEach((h) => {
const item: TocItem = { ...h, text: removeTailingHash(h.text), children: [] };
while (parentStack.length > 0 && parentStack[parentStack.length - 1].depth >= item.depth) {
parentStack.pop();
}
if (parentStack.length > 0) {
parentStack[parentStack.length - 1].children.push(item);
} else {
toc.push(item);
}
parentStack.push(item);
});
return toc;
}
const toc = buildToc(headings);
---
{isPostsRoute &&
<table-of-contents class:list={[className, "group block relative z-0 bg-[var(--float-panel-bg-opaque)] rounded-xl p-2"]}>
{headings.filter((heading) => heading.depth < minDepth + maxLevel).map((heading) =>
<a href={`#${heading.slug}`} class="px-2 flex gap-2 relative transition w-full min-h-9 rounded-xl
hover:bg-[var(--toc-btn-hover)] active:bg-[var(--toc-btn-active)] py-2
">
<div class:list={["transition w-5 h-5 shrink-0 rounded-lg text-xs flex items-center justify-center font-bold",
{
"bg-[var(--toc-badge-bg)] text-[var(--btn-content)]": heading.depth == minDepth,
"ml-4": heading.depth == minDepth + 1,
"ml-8": heading.depth == minDepth + 2,
}
]}
>
{heading.depth == minDepth && heading1Count++}
{heading.depth == minDepth + 1 && <div class="transition w-2 h-2 rounded-[0.1875rem] bg-[var(--toc-badge-bg)]"></div>}
{heading.depth == minDepth + 2 && <div class="transition w-1.5 h-1.5 rounded-sm bg-black/5 dark:bg-white/10"></div>}
</div>
<div class:list={["transition text-sm", {
"text-50": heading.depth == minDepth || heading.depth == minDepth + 1,
"text-30": heading.depth == minDepth + 2,
}]}>{removeTailingHash(heading.text)}</div>
</a>
)}
<TOCList items={toc} />
<div id="active-indicator" class:list={[{'hidden': headings.length == 0}, "-z-10 absolute bg-[var(--toc-btn-hover)] left-2 right-2 rounded-xl transition-all " +
"group-hover:bg-transparent border-2 border-[var(--toc-btn-hover)] group-hover:border-[var(--toc-btn-active)] border-dashed"]}></div>
</table-of-contents>}

View File

@@ -0,0 +1,72 @@
---
import type { MarkdownHeading } from "astro";
interface TocItem extends MarkdownHeading {
children: TocItem[];
}
interface Props {
items: TocItem[];
level?: number;
}
const { items, level = 0 } = Astro.props;
---
{
items.length > 0 && (
level === 0 ? (
<ol class="space-y-1">
{items.map((item, index) => (
<li>
<a href={`#${item.slug}`} class="px-2 flex gap-2 relative transition w-full min-h-9 rounded-xl hover:bg-[var(--toc-btn-hover)] active:bg-[var(--toc-btn-active)] py-2">
<div class:list={["transition w-5 h-5 shrink-0 rounded-lg text-xs flex items-center justify-center font-bold",
{
"bg-[var(--toc-badge-bg)] text-[var(--btn-content)]": level === 0,
}
]}>
{level === 0 && index + 1}
{level === 1 && <div class="transition w-2 h-2 rounded-[0.1875rem] bg-[var(--toc-badge-bg)]"></div>}
{level === 2 && <div class="transition w-1.5 h-1.5 rounded-sm bg-black/5 dark:bg-white/10"></div>}
{level > 2 && <div class="transition w-1 h-1 rounded-sm bg-black/5 dark:bg-white/10"></div>}
</div>
<div class:list={["transition text-sm", {
"text-50": level === 0 || level === 1,
"text-30": level >= 2,
}]}>{item.text}</div>
</a>
{item.children.length > 0 && (
<Astro.self items={item.children} level={level + 1} />
)}
</li>
))}
</ol>
) : (
<ul class="pl-4 space-y-1">
{items.map((item) => (
<li>
<a href={`#${item.slug}`} class="px-2 flex gap-2 relative transition w-full min-h-9 rounded-xl hover:bg-[var(--toc-btn-hover)] active:bg-[var(--toc-btn-active)] py-2">
<div class:list={["transition w-5 h-5 shrink-0 rounded-lg text-xs flex items-center justify-center font-bold",
{
"bg-[var(--toc-badge-bg)] text-[var(--btn-content)]": level === 0,
}
]}>
{level === 0 && "0"} {/* Should not happen for ul if level 0 is ol */}
{level === 1 && <div class="transition w-2 h-2 rounded-[0.1875rem] bg-[var(--toc-badge-bg)]"></div>}
{level === 2 && <div class="transition w-1.5 h-1.5 rounded-sm bg-black/5 dark:bg-white/10"></div>}
{level > 2 && <div class="transition w-1 h-1 rounded-sm bg-black/5 dark:bg-white/10"></div>}
</div>
<div class:list={["transition text-sm", {
"text-50": level === 0 || level === 1,
"text-30": level >= 2,
}]}>{item.text}</div>
</a>
{item.children.length > 0 && (
<Astro.self items={item.children} level={level + 1} />
)}
</li>
))}
</ul>
)
)
}