"use client";
import Image from "next/image";
import Link from "next/link";
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, 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;
};
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}
) : 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 [svg, setSvg] = useState("");
const [error, setError] = useState("");
const idRef = useRef(`mermaid-${Math.random().toString(36).slice(2)}`);
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("Mermaid diyagramı oluşturulamadı.");
}
}
};
void renderChart();
return () => {
mounted = false;
};
}, [chart]);
if (error) {
return (
{error}
);
}
if (!svg) {
return (
Diyagram hazırlanıyor...
);
}
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 }: BlogDetailContentProps) {
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 (
<>
{"<- Blog'a dön"}
{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: any) => c.tagName === "img",
);
if (hasImg) {
return (
{children}
);
}
return (
{children}
);
},
ul: ({ children }) => (
),
ol: ({ children }) => (
{children}
),
li: ({ children }) => (
{children}
),
a: ({ href, children }) => (
{children}
),
blockquote: ({ children }) => (
{children}
),
hr: () => ,
table: ({ 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 && (
)}
{/* Mobil İçindekiler Butonu */}
{/* Mobil İçindekiler Drawer */}
{tocOpen && (
e.stopPropagation()}
>
İçindekiler
)}
>
);
}