mirror of
https://github.com/stefanpejcic/openpanel
synced 2025-06-26 18:28:26 +00:00
140 lines
3.7 KiB
TypeScript
140 lines
3.7 KiB
TypeScript
import React from "react";
|
|
import clsx from "clsx";
|
|
import { useLocation, useHistory } from "@docusaurus/router";
|
|
import { Tags } from "@site/src/components/blog";
|
|
|
|
export const BlogTOCItem = ({
|
|
id,
|
|
value,
|
|
level,
|
|
activeId,
|
|
onIdChange,
|
|
}: {
|
|
id: string;
|
|
value: string;
|
|
level: number;
|
|
activeId: string;
|
|
onIdChange?: (id: string) => void;
|
|
}) => {
|
|
React.useEffect(() => {
|
|
const targetElement = document.getElementById(id);
|
|
|
|
if (targetElement) {
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
entries.forEach((entry) => {
|
|
if (entry.isIntersecting) {
|
|
onIdChange(id);
|
|
}
|
|
});
|
|
},
|
|
{
|
|
rootMargin: "0px 0px -80% 0px",
|
|
},
|
|
);
|
|
|
|
observer.observe(targetElement);
|
|
|
|
return () => {
|
|
observer.unobserve(targetElement);
|
|
};
|
|
}
|
|
}, [id]);
|
|
|
|
return (
|
|
<a
|
|
href={`#${id}`}
|
|
className={clsx(
|
|
"refine-toc-item",
|
|
level === 2 && "pl-3",
|
|
level === 3 && "pl-7",
|
|
level === 4 && "pl-11",
|
|
"py-2 pr-3",
|
|
activeId === id &&
|
|
` dark:bg-gray-700 bg-gray-100 text-gray-900 dark:text-gray-0 hover:text-gray-900`,
|
|
activeId !== id &&
|
|
`dark:text-gray-500 hover:text-gray-900 text-gray-500`,
|
|
"rounded-lg",
|
|
"transition-colors duration-200 ease-in-out",
|
|
"block",
|
|
"text-sm",
|
|
"leading-6",
|
|
"no-underline hover:no-underline",
|
|
)}
|
|
dangerouslySetInnerHTML={{
|
|
__html: value,
|
|
}}
|
|
/>
|
|
);
|
|
};
|
|
|
|
export const BlogTOC = (props) => {
|
|
const history = useHistory();
|
|
const location = useLocation();
|
|
const { toc, hasTOC, activeId, setActiveId } = useTOC(props.toc);
|
|
|
|
const onIdChange = (id) => {
|
|
if (id !== `${location.hash ?? ""}`.replace("#", "")) {
|
|
setActiveId(id);
|
|
window.history.replaceState({}, "", `#${id}`);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={clsx(
|
|
"hidden blog-md:block",
|
|
"w-full",
|
|
"sticky right-0 top-[67px]",
|
|
"max-w-[280px]",
|
|
"overflow-auto",
|
|
"h-[calc(100vh-67px)]",
|
|
"px-3",
|
|
"blog-md:py-16",
|
|
"border-l dark:border-l-gray-700 border-l-gray-100",
|
|
!hasTOC && "invisible",
|
|
)}
|
|
>
|
|
<Tags />
|
|
<ul className="list-none m-0 p-0">
|
|
{toc.map((item) => {
|
|
return (
|
|
<li key={item.id}>
|
|
<BlogTOCItem
|
|
id={item.id}
|
|
value={item.value}
|
|
activeId={activeId}
|
|
level={item.level}
|
|
onIdChange={onIdChange}
|
|
/>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export const useTOC = (toc) => {
|
|
const { hash } = useLocation();
|
|
|
|
const baseActiveId = `${hash}`.replace("#", "");
|
|
|
|
const [activeId, setActiveId] = React.useState<string | undefined>(
|
|
baseActiveId,
|
|
);
|
|
|
|
React.useEffect(() => {
|
|
setActiveId(baseActiveId);
|
|
}, [baseActiveId]);
|
|
|
|
const hasTOC = toc?.length > 0;
|
|
|
|
return {
|
|
toc,
|
|
activeId,
|
|
setActiveId,
|
|
hasTOC,
|
|
};
|
|
};
|