refactor: Migrate documentation content, rebuild UI components, and update core architecture.

This commit is contained in:
gitfromwildan
2026-03-10 01:38:58 +07:00
parent aac81dff8a
commit ab755844a3
132 changed files with 3947 additions and 12862 deletions

View File

@@ -1,68 +1,80 @@
import { useState, useCallback, useEffect, useRef } from 'react';
import { TocItem } from '@/lib/toc';
"use client"
import { useState, useCallback, useEffect, useRef } from "react"
import { TocItem } from "@/lib/toc"
export function useActiveSection(tocs: TocItem[]) {
const [activeId, setActiveId] = useState<string | null>(null);
const observerRef = useRef<IntersectionObserver | null>(null);
const clickedIdRef = useRef<string | null>(null);
const [activeId, setActiveId] = useState<string | null>(null)
const observerRef = useRef<IntersectionObserver | null>(null)
const clickedIdRef = useRef<string | null>(null)
const activeIdRef = useRef<string | null>(null)
useEffect(() => {
activeIdRef.current = activeId
}, [activeId])
// Handle intersection observer for active section
useEffect(() => {
if (typeof document === 'undefined' || !tocs.length) return;
if (typeof document === "undefined" || !tocs.length) return
const handleIntersect = (entries: IntersectionObserverEntry[]) => {
if (clickedIdRef.current) return;
if (clickedIdRef.current) return
const visibleEntries = entries.filter(entry => entry.isIntersecting);
if (!visibleEntries.length) return;
const visibleEntries = entries.filter((entry) => entry.isIntersecting)
if (!visibleEntries.length) return
// Find the most visible entry
const mostVisibleEntry = visibleEntries.reduce((prev, current) => {
return current.intersectionRatio > prev.intersectionRatio ? current : prev;
}, visibleEntries[0]);
return current.intersectionRatio > prev.intersectionRatio ? current : prev
}, visibleEntries[0])
const newActiveId = mostVisibleEntry.target.id;
if (newActiveId !== activeId) {
setActiveId(newActiveId);
const newActiveId = mostVisibleEntry.target.id
if (newActiveId !== activeIdRef.current) {
setActiveId(newActiveId)
}
};
}
// Determine the scroll root: #scroll-container is only used on desktop (lg)
const isDesktop = window.innerWidth >= 1024
const container = isDesktop ? document.getElementById("scroll-container") : null
// Initialize intersection observer
observerRef.current = new IntersectionObserver(handleIntersect, {
root: null,
rootMargin: '0px 0px -80% 0px',
threshold: 0.1,
});
root: container,
rootMargin: isDesktop ? "0px 0px -60% 0px" : "-160px 0px -60% 0px",
threshold: 0,
})
// Observe all headings
tocs.forEach(toc => {
const element = document.getElementById(toc.href.slice(1));
tocs.forEach((toc) => {
const element = document.getElementById(toc.href.slice(1))
if (element) {
observerRef.current?.observe(element);
observerRef.current?.observe(element)
}
});
})
// Cleanup
return () => {
observerRef.current?.disconnect();
};
}, [tocs, activeId]);
observerRef.current?.disconnect()
}
}, [tocs]) // Only depend on tocs, handle activeId via ref
const handleLinkClick = useCallback((id: string) => {
clickedIdRef.current = id;
setActiveId(id);
clickedIdRef.current = id
setActiveId(id)
// Reset clicked state after scroll completes
const timer = setTimeout(() => {
clickedIdRef.current = null;
}, 1000);
clickedIdRef.current = null
}, 1000)
return () => clearTimeout(timer);
}, []);
return () => clearTimeout(timer)
}, [])
return {
activeId,
setActiveId,
handleLinkClick,
};
}
}