"use client"; import { useEffect, useState } from "react"; import { useEditor, EditorContent } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; import Placeholder from "@tiptap/extension-placeholder"; import { Markdown } from "tiptap-markdown"; import { Bold, Code, Heading2, Heading3, Italic, List, ListOrdered } from "lucide-react"; import { cn } from "@/lib/utils"; type Props = { value: string; onChange: (v: string) => void; placeholder?: string; className?: string; }; 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"; export function RichEditor({ value, onChange, placeholder, className }: Props) { const [tab, setTab] = useState<"edit" | "preview">("edit"); const [preview, setPreview] = useState(""); const editor = useEditor({ extensions: [ StarterKit.configure({ codeBlock: false }), Placeholder.configure({ placeholder: placeholder ?? "توضیحات را اینجا بنویسید…" }), Markdown.configure({ transformPastedText: true }), ], content: value, immediatelyRender: false, onUpdate({ editor: e }) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const md = (e.storage as any).markdown?.getMarkdown?.() ?? e.getText(); onChange(md); }, }); useEffect(() => { if (!editor) return; // eslint-disable-next-line @typescript-eslint/no-explicit-any const current = (editor.storage as any).markdown?.getMarkdown?.() ?? editor.getText(); if (current !== value) { editor.commands.setContent(value, { emitUpdate: false }); } }, [value, editor]); useEffect(() => { if (tab !== "preview") return; let cancelled = false; void (async () => { const { marked } = await import("marked"); const DOMPurify = (await import("dompurify")).default; const html = await marked(value); if (!cancelled) setPreview(DOMPurify.sanitize(html)); })(); return () => { cancelled = true; }; }, [tab, value]); const ToolBtn = ({ onClick, active, title, children, }: { onClick: () => void; active?: boolean; title: string; children: React.ReactNode; }) => ( ); return (
{/* Tab bar */}
{editor && ( <> editor.chain().focus().toggleBold().run()} active={editor.isActive("bold")} title="توپر" > editor.chain().focus().toggleItalic().run()} active={editor.isActive("italic")} title="مورب" > editor.chain().focus().toggleCode().run()} active={editor.isActive("code")} title="کد درون‌خطی" > editor.chain().focus().toggleHeading({ level: 2 }).run()} active={editor.isActive("heading", { level: 2 })} title="سرتیتر ۲" > editor.chain().focus().toggleHeading({ level: 3 }).run()} active={editor.isActive("heading", { level: 3 })} title="سرتیتر ۳" > editor.chain().focus().toggleBulletList().run()} active={editor.isActive("bulletList")} title="فهرست" > editor.chain().focus().toggleOrderedList().run()} active={editor.isActive("orderedList")} title="فهرست عددی" > )}
{/* Editor / Preview */} {tab === "edit" ? ( ) : (
پیش‌نمایشی نیست.

", }} /> )}
); }