2026-02-26 14:24:48 +08:00
|
|
|
import { Component, createSignal, onMount, Show } from "solid-js";
|
|
|
|
|
import { generateToc, type FileNode, type TocNode } from "../data-loader";
|
2026-02-26 15:04:17 +08:00
|
|
|
import { useLocation } from "@solidjs/router";
|
|
|
|
|
import { FileTreeNode, HeadingNode } from "./FileTree";
|
2026-02-26 14:24:48 +08:00
|
|
|
|
|
|
|
|
interface SidebarProps {
|
|
|
|
|
isOpen: boolean;
|
|
|
|
|
onClose: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 侧边栏组件
|
|
|
|
|
*/
|
|
|
|
|
export const Sidebar: Component<SidebarProps> = (props) => {
|
|
|
|
|
const location = useLocation();
|
|
|
|
|
const [fileTree, setFileTree] = createSignal<FileNode[]>([]);
|
|
|
|
|
const [pathHeadings, setPathHeadings] = createSignal<
|
|
|
|
|
Record<string, TocNode[]>
|
|
|
|
|
>({});
|
|
|
|
|
const [currentFileHeadings, setCurrentFileHeadings] = createSignal<TocNode[]>(
|
|
|
|
|
[],
|
|
|
|
|
);
|
|
|
|
|
|
2026-02-26 14:30:09 +08:00
|
|
|
onMount(async () => {
|
|
|
|
|
const toc = await generateToc();
|
2026-02-26 14:24:48 +08:00
|
|
|
setFileTree(toc.fileTree);
|
|
|
|
|
setPathHeadings(toc.pathHeadings);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 根据当前路径更新当前文件的标题列表
|
|
|
|
|
onMount(() => {
|
|
|
|
|
const updateHeadings = () => {
|
|
|
|
|
const pathname = location.pathname;
|
2026-02-26 14:57:18 +08:00
|
|
|
const headings =
|
|
|
|
|
pathHeadings()[pathname] || pathHeadings()[`${pathname}.md`];
|
2026-02-26 14:24:48 +08:00
|
|
|
setCurrentFileHeadings(headings || []);
|
|
|
|
|
};
|
|
|
|
|
updateHeadings();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return (
|
2026-02-26 15:02:28 +08:00
|
|
|
<>
|
2026-02-26 14:24:48 +08:00
|
|
|
{/* 遮罩层 */}
|
|
|
|
|
<div
|
2026-02-26 15:02:28 +08:00
|
|
|
class={`fixed inset-0 bg-black/50 z-40 transition-opacity duration-300 ease-in-out ${
|
|
|
|
|
props.isOpen ? "opacity-100" : "opacity-0 pointer-events-none"
|
|
|
|
|
}`}
|
2026-02-26 14:24:48 +08:00
|
|
|
onClick={props.onClose}
|
|
|
|
|
/>
|
|
|
|
|
{/* 侧边栏 */}
|
2026-02-26 15:02:28 +08:00
|
|
|
<aside
|
|
|
|
|
class={`fixed top-0 left-0 h-full w-64 bg-white shadow-lg z-50 overflow-y-auto transform transition-transform duration-300 ease-in-out ${
|
|
|
|
|
props.isOpen ? "translate-x-0" : "-translate-x-full"
|
|
|
|
|
}`}
|
|
|
|
|
>
|
2026-02-26 14:24:48 +08:00
|
|
|
<div class="p-4">
|
|
|
|
|
<div class="flex items-center justify-between mb-4">
|
|
|
|
|
<h2 class="text-lg font-bold text-gray-900">目录</h2>
|
|
|
|
|
<button
|
|
|
|
|
onClick={props.onClose}
|
|
|
|
|
class="text-gray-500 hover:text-gray-700"
|
|
|
|
|
title="关闭"
|
|
|
|
|
>
|
|
|
|
|
✕
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 文件树 */}
|
|
|
|
|
<div class="mb-4">
|
|
|
|
|
<h3 class="text-xs font-semibold text-gray-500 uppercase mb-2 px-2">
|
|
|
|
|
文件
|
|
|
|
|
</h3>
|
|
|
|
|
{fileTree().map((node) => (
|
|
|
|
|
<FileTreeNode
|
|
|
|
|
node={node}
|
|
|
|
|
currentPath={location.pathname}
|
|
|
|
|
pathHeadings={pathHeadings()}
|
|
|
|
|
depth={0}
|
|
|
|
|
onClose={props.onClose}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 当前文件标题 */}
|
|
|
|
|
<Show when={currentFileHeadings().length > 0}>
|
|
|
|
|
<div>
|
|
|
|
|
<h3 class="text-xs font-semibold text-gray-500 uppercase mb-2 px-2">
|
|
|
|
|
本页
|
|
|
|
|
</h3>
|
|
|
|
|
{currentFileHeadings().map((node) => (
|
|
|
|
|
<HeadingNode
|
|
|
|
|
node={node}
|
|
|
|
|
basePath={location.pathname}
|
|
|
|
|
depth={0}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</Show>
|
|
|
|
|
</div>
|
|
|
|
|
</aside>
|
2026-02-26 15:02:28 +08:00
|
|
|
</>
|
2026-02-26 14:24:48 +08:00
|
|
|
);
|
|
|
|
|
};
|