"use client"; import Image from "next/image"; import { Icon } from "@iconify/react"; import { useTranslations } from "next-intl"; import React, { useCallback, useEffect, useRef, useState } from "react"; import ReactMarkdown from "react-markdown"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism"; import remarkGfm from "remark-gfm"; import { Badge, Card, Typography } from "poyraz-ui/atoms"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "poyraz-ui/molecules"; import { ArticleToc } from "@/components/article-toc"; import type { AnimationSource } from "@/data/animation-sources"; import { Link } from "@/i18n/routing"; import { slugifyMarkdownHeading } from "@/lib/markdown-headings"; type AnimationSourceDetailContentProps = { source: AnimationSource; }; function extractText(children: React.ReactNode): string { if (typeof children === "string" || typeof children === "number") { return String(children); } if (Array.isArray(children)) return children.map(extractText).join(""); if (children && typeof children === "object" && "props" in children) { return extractText( (children as React.ReactElement<{ children?: React.ReactNode }>).props .children, ); } return ""; } async function copyToClipboard(value: string) { if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(value); return; } const textarea = document.createElement("textarea"); textarea.value = value; textarea.setAttribute("readonly", ""); textarea.style.position = "fixed"; textarea.style.opacity = "0"; document.body.appendChild(textarea); textarea.select(); document.execCommand("copy"); textarea.remove(); } function CopyableCodeBlock({ code, language, }: { code: string; language: string; }) { const t = useTranslations("AnimationSources"); const [copied, setCopied] = useState(false); const resetTimerRef = useRef(null); useEffect(() => { return () => { if (resetTimerRef.current !== null) { window.clearTimeout(resetTimerRef.current); } }; }, []); const handleCopy = async () => { try { await copyToClipboard(code); setCopied(true); if (resetTimerRef.current !== null) { window.clearTimeout(resetTimerRef.current); } resetTimerRef.current = window.setTimeout(() => setCopied(false), 1800); } catch { setCopied(false); } }; const displayLanguage = language || "text"; const highlighterLanguage = language === "prompt" ? "text" : displayLanguage; return (
{displayLanguage} {copied ? t("copied") : t("copy")}
{code}
); } function MarkdownImage({ src, alt }: { src?: string; alt?: string }) { if (!src || !src.startsWith("/")) return null; return ( {alt {alt ? ( {alt} ) : null} ); } export function AnimationSourceDetailContent({ source, }: AnimationSourceDetailContentProps) { const t = useTranslations("AnimationSources"); const progressBarRef = useRef(null); const [tocOpen, setTocOpen] = useState(false); const closeToc = useCallback(() => setTocOpen(false), []); useEffect(() => { let rafId = 0; const update = () => { rafId = 0; const bar = progressBarRef.current; if (!bar) return; const scrollableHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight; const progress = scrollableHeight <= 0 ? 100 : (window.scrollY / scrollableHeight) * 100; bar.style.width = `${Math.min(100, Math.max(0, progress))}%`; }; const handleScroll = () => { if (rafId !== 0) return; rafId = window.requestAnimationFrame(update); }; window.addEventListener("scroll", handleScroll, { passive: true }); update(); return () => { window.removeEventListener("scroll", handleScroll); if (rafId !== 0) window.cancelAnimationFrame(rafId); }; }, []); return ( <>
{t("back")}
{source.platform} {source.tools.map((tool) => ( {tool} ))}
{source.title} {source.excerpt} {source.author} ยท {source.date}
( {children} ), h2: ({ children }) => ( {children} ), h3: ({ children }) => ( {children} ), p: ({ node, children }) => { const containsImage = node?.children.some( (child) => child.type === "element" && child.tagName === "img", ); if (containsImage) { return
{children}
; } return ( {children} ); }, ul: ({ children }) => (
    {children}
), ol: ({ children }) => (
    {children}
), a: ({ href, children }) => ( {children} ), blockquote: ({ children }) => (
{children}
), hr: () =>
, table: ({ children }) => ( {children}
), th: ({ children }) => ( {children} ), td: ({ children }) => ( {children} ), img: ({ src, alt }) => ( ), pre: ({ children }) => <>{children}, code: ({ className, children }) => { const match = /language-([\w-]+)/.exec(className ?? ""); if (!match) { return ( {children} ); } return ( ); }, }} > {source.markdown}
{tocOpen ? (
event.stopPropagation()} >
{t("toc")}
) : null} ); }