feat: enhance components with new features and optimizations

- Added support for conditional rendering of the NekoFollower component in AppShell based on ENABLE_NEKO_FOLLOWER.
- Introduced MarkdownImage component in BlogDetailContent for handling images in markdown, supporting both local and external assets.
- Improved BlogToc component to use IntersectionObserver for better performance in tracking active headings.
- Refactored ContentContent to utilize YoutubeLiteEmbed for embedding YouTube videos, simplifying the video rendering logic.
- Created a new YoutubeLiteEmbed component for lazy loading YouTube videos with thumbnail previews.
- Updated site-settings to include ENABLE_NEKO_FOLLOWER constant for feature toggling.
- Enhanced next.config.ts with image optimization settings for better performance.
- Refactored snippets-content to improve code readability and structure.
- Cleaned up home-hero and home-videos-section components for better layout and performance.
This commit is contained in:
Poyraz Avsever
2026-04-15 10:38:28 +03:00
parent 5fc2658460
commit 0609170073
11 changed files with 438 additions and 207 deletions
+47 -27
View File
@@ -44,49 +44,66 @@ export function BlogToc({ markdown, onNavigate }: BlogTocProps) {
const [activeId, setActiveId] = useState("");
const rafRef = useRef(0);
const handleClick = useCallback((id: string) => {
const target = document.getElementById(id);
if (!target) return;
const handleClick = useCallback(
(id: string) => {
const target = document.getElementById(id);
if (!target) return;
target.scrollIntoView({ behavior: "smooth", block: "start" });
setActiveId(id);
onNavigate?.();
}, [onNavigate]);
target.scrollIntoView({ behavior: "smooth", block: "start" });
setActiveId(id);
onNavigate?.();
},
[onNavigate],
);
useEffect(() => {
if (headings.length === 0) return;
const OFFSET = 120;
const headingElements = headings
.map((heading) => ({
id: heading.id,
element: document.getElementById(heading.id),
}))
.filter((item): item is { id: string; element: HTMLElement } =>
Boolean(item.element),
);
if (headingElements.length === 0) return;
const updateActive = () => {
let current = "";
let current = headingElements[0].id;
for (const heading of headings) {
const el = document.getElementById(heading.id);
if (!el) continue;
const top = el.getBoundingClientRect().top;
if (top <= OFFSET) {
current = heading.id;
for (const item of headingElements) {
if (item.element.getBoundingClientRect().top <= 120) {
current = item.id;
continue;
}
break;
}
if (current) {
setActiveId(current);
}
setActiveId(current);
};
const onScroll = () => {
cancelAnimationFrame(rafRef.current);
rafRef.current = requestAnimationFrame(updateActive);
};
const observer = new IntersectionObserver(
() => {
cancelAnimationFrame(rafRef.current);
rafRef.current = requestAnimationFrame(updateActive);
},
{
root: null,
rootMargin: "-120px 0px -65% 0px",
threshold: [0, 1],
},
);
window.addEventListener("scroll", onScroll, { passive: true });
for (const item of headingElements) {
observer.observe(item.element);
}
const timer = setTimeout(updateActive, 300);
const timer = setTimeout(updateActive, 200);
return () => {
window.removeEventListener("scroll", onScroll);
observer.disconnect();
cancelAnimationFrame(rafRef.current);
clearTimeout(timer);
};
@@ -96,7 +113,10 @@ export function BlogToc({ markdown, onNavigate }: BlogTocProps) {
return (
<nav aria-label="İçindekiler" className="space-y-1">
<Typography variant="small" className="mb-2 font-semibold text-foreground">
<Typography
variant="small"
className="mb-2 font-semibold text-foreground"
>
İçindekiler
</Typography>
{headings.map((heading, index) => (