mirror of
https://github.com/afoim/fuwari.git
synced 2026-01-31 09:03:18 +08:00
feat(组件): 实现可递归的TOC列表组件并重构目录结构
- 新增TOCList.astro组件用于递归渲染多级目录 - 重构TOC.astro组件逻辑,使用buildToc函数构建树形结构 - 移除原有平铺式目录渲染方式,改为组件化递归实现
This commit is contained in:
@@ -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>}
|
||||
|
||||
72
src/components/widget/TOCList.astro
Normal file
72
src/components/widget/TOCList.astro
Normal 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>
|
||||
)
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user