"use client"; import Image from "next/image"; import { Link } from "@/i18n/routing"; import { useTranslations } from "next-intl"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism"; import { useCallback, useEffect, useId, useRef, useState } from "react"; import { Icon } from "@iconify/react"; import { Badge, Card, Typography } from "poyraz-ui/atoms"; import type { BlogDetail } from "@/data/blog-detail"; import { BlogToc } from "@/components/blog-toc"; import { GiscusComments } from "@/components/giscus-comments"; type BlogDetailContentProps = { post: BlogDetail; section?: "blog" | "agenda"; }; function isHttpUrl(value: string) { return value.startsWith("http://") || value.startsWith("https://"); } function MarkdownImage({ src, alt }: { src?: string; alt?: string }) { if (!src) return null; const caption = alt || ""; const isLocalAsset = src.startsWith("/"); const isExternalAsset = isHttpUrl(src); if (!isLocalAsset && !isExternalAsset) return null; return ( {isLocalAsset ? ( {caption} ) : ( {caption} )} {caption ? (
{caption}
) : null}
); } function slugify(text: string) { return text .toLowerCase() .replace(/\*\*/g, "") .replace(/\*/g, "") .replace(/[^a-zçğıöşü0-9\s-]/g, "") .replace(/\s+/g, "-") .replace(/-+/g, "-") .replace(/^-|-$/g, ""); } function extractText(children: React.ReactNode): string { if (typeof children === "string") return 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 ""; } function MermaidBlock({ chart }: { chart: string }) { const t = useTranslations("Blog"); const [svg, setSvg] = useState(""); const [error, setError] = useState(""); const generatedId = useId(); const idRef = useRef(`mermaid-${generatedId.replace(/:/g, "")}`); useEffect(() => { let mounted = true; const renderChart = async () => { try { const mermaid = (await import("mermaid")).default; mermaid.initialize({ startOnLoad: false, theme: "neutral", securityLevel: "loose", }); const { svg: rendered } = await mermaid.render(idRef.current, chart); if (mounted) { setSvg(rendered); setError(""); } } catch { if (mounted) { setError(t("diagramError")); } } }; void renderChart(); return () => { mounted = false; }; }, [chart, t]); if (error) { return ( {error} ); } if (!svg) { return ( {t("diagramLoading")} ); } return (
); } const GISCUS_REPO = process.env.NEXT_PUBLIC_GISCUS_REPO || ""; const GISCUS_REPO_ID = process.env.NEXT_PUBLIC_GISCUS_REPO_ID || ""; const GISCUS_CATEGORY = process.env.NEXT_PUBLIC_GISCUS_CATEGORY || "Announcements"; const GISCUS_CATEGORY_ID = process.env.NEXT_PUBLIC_GISCUS_CATEGORY_ID || ""; export function BlogDetailContent({ post, section = "blog" }: BlogDetailContentProps) { const t = useTranslations("Blog"); const agendaT = useTranslations("Agenda"); const progressBarRef = useRef(null); const [tocOpen, setTocOpen] = useState(false); const closeToc = useCallback(() => setTocOpen(false), []); useEffect(() => { const update = () => { const bar = progressBarRef.current; if (!bar) return; const docHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight; const pct = docHeight <= 0 ? 100 : (window.scrollY / docHeight) * 100; bar.style.width = `${Math.min(100, Math.max(0, pct))}%`; }; window.addEventListener("scroll", update, { passive: true }); update(); return () => window.removeEventListener("scroll", update); }, []); const showGiscus = GISCUS_REPO && GISCUS_REPO_ID && GISCUS_CATEGORY_ID; return ( <>
{section === "agenda" ? agendaT("backToAgenda") : t("backToBlog")}
{post.category} {post.author} · {post.date} · {post.readTime}
{ const text = extractText(children); const id = slugify(text); return ( {children} ); }, h2: ({ children }) => { const text = extractText(children); const id = slugify(text); return ( {children} ); }, h3: ({ children }) => { const text = extractText(children); const id = slugify(text); return ( {children} ); }, h4: ({ children }) => { const text = extractText(children); const id = slugify(text); return ( {children} ); }, p: ({ node, children, ...props }) => { const hasImg = node?.children?.some( (c: unknown) => (c as { tagName?: string }).tagName === "img", ); if (hasImg) { return (
{children}
); } return ( {children} ); }, ul: ({ children }) => (
    {children}
), ol: ({ children }) => (
    {children}
), li: ({ children }) => (
  • {children}
  • ), a: ({ href, children }) => ( {children} ), blockquote: ({ children }) => (
    {children}
    ), hr: () =>
    , table: ({ children }) => (
    {children}
    ), thead: ({ children }) => ( {children} ), tr: ({ children }) => ( {children} ), th: ({ children }) => ( {children} ), td: ({ children }) => ( {children} ), img: ({ src, alt }) => ( ), code: ({ className, children }) => { const match = /language-(\w+)/.exec(className ?? ""); const codeText = String(children).replace(/\n$/, ""); const language = match?.[1] ?? ""; if (!match) { return ( {children} ); } if (language === "mermaid") { return ; } return (
    {language || "code"}
    {codeText}
    ); }, }} > {post.markdown}
    {showGiscus && (
    {t("comments")}
    )}
    {/* Mobil İçindekiler Butonu */} {/* Mobil İçindekiler Drawer */} {tocOpen && (
    e.stopPropagation()} >
    {t("toc")}
    )} ); }