2026-10-10 16:42:35 +03:30
|
|
|
"use client";
|
|
|
|
|
|
2026-10-18 10:14:22 +03:30
|
|
|
import { useEffect, useState } from "react";
|
2026-10-10 16:42:35 +03:30
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
|
|
|
|
|
|
export function SafeMarkdown({
|
|
|
|
|
text,
|
|
|
|
|
className,
|
|
|
|
|
lines,
|
|
|
|
|
}: {
|
|
|
|
|
text: string;
|
|
|
|
|
className?: string;
|
|
|
|
|
lines?: 2 | 3;
|
|
|
|
|
}) {
|
2026-10-18 10:14:22 +03:30
|
|
|
const [html, setHtml] = useState("");
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!text) { setHtml(""); return; }
|
2026-10-18 10:29:58 +03:30
|
|
|
let cancelled = false;
|
2026-10-18 10:14:22 +03:30
|
|
|
void (async () => {
|
|
|
|
|
const { marked } = await import("marked");
|
|
|
|
|
const DOMPurify = (await import("dompurify")).default;
|
|
|
|
|
const raw = await marked(text);
|
2026-10-18 10:29:58 +03:30
|
|
|
if (!cancelled) setHtml(DOMPurify.sanitize(raw));
|
2026-10-18 10:14:22 +03:30
|
|
|
})();
|
2026-10-18 10:29:58 +03:30
|
|
|
return () => { cancelled = true; };
|
2026-10-18 10:14:22 +03:30
|
|
|
}, [text]);
|
2026-10-10 16:42:35 +03:30
|
|
|
|
2026-10-18 10:29:58 +03:30
|
|
|
const mdClasses =
|
|
|
|
|
"[&_strong]:font-bold [&_em]:italic [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:text-[0.85em] [&_h2]:text-base [&_h2]:font-bold [&_h2]:mt-3 [&_h3]:text-sm [&_h3]:font-bold [&_h3]:mt-2 [&_ul]:list-disc [&_ul]:pr-5 [&_ol]:list-decimal [&_ol]:pr-5 [&_li]:mt-0.5 [&_p]:leading-relaxed [&_p+p]:mt-2 [&_a]:underline [&_a]:text-primary";
|
|
|
|
|
|
2026-10-18 10:14:22 +03:30
|
|
|
if (!html) {
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
dir="auto"
|
|
|
|
|
className={cn(
|
|
|
|
|
"break-words text-sm [unicode-bidi:plaintext]",
|
|
|
|
|
lines === 2 && "line-clamp-2",
|
|
|
|
|
lines === 3 && "line-clamp-3",
|
|
|
|
|
!lines && "whitespace-pre-wrap",
|
|
|
|
|
className,
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{text}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2026-10-10 16:42:35 +03:30
|
|
|
}
|
2026-10-18 10:14:22 +03:30
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
dir="auto"
|
|
|
|
|
className={cn(
|
2026-10-18 10:29:58 +03:30
|
|
|
"break-words text-sm [unicode-bidi:plaintext]",
|
|
|
|
|
mdClasses,
|
2026-10-18 10:14:22 +03:30
|
|
|
lines === 2 && "line-clamp-2",
|
|
|
|
|
lines === 3 && "line-clamp-3",
|
|
|
|
|
className,
|
|
|
|
|
)}
|
|
|
|
|
dangerouslySetInnerHTML={{ __html: html }}
|
|
|
|
|
/>
|
|
|
|
|
);
|
2026-10-10 16:42:35 +03:30
|
|
|
}
|