"use client" import { useEffect } from "react" import { useEditor, EditorContent } from "@tiptap/react" import StarterKit from "@tiptap/starter-kit" import Link from "@tiptap/extension-link" import Placeholder from "@tiptap/extension-placeholder" import { cn } from "@/lib/utils" import sanitize from "sanitize-html" import { Button } from "@/components/ui/button" import { Separator } from "@/components/ui/separator" import { Bold, Italic, Strikethrough, List, ListOrdered, Quote, Undo, Redo, Link as LinkIcon, } from "lucide-react" type RichTextEditorProps = { value?: string onChange?: (html: string) => void className?: string placeholder?: string disabled?: boolean minHeight?: number } export function RichTextEditor({ value, onChange, className, placeholder = "Escreva aqui...", disabled, minHeight = 120, }: RichTextEditorProps) { const editor = useEditor({ extensions: [ StarterKit.configure({ bulletList: { keepMarks: true }, orderedList: { keepMarks: true }, }), Link.configure({ openOnClick: true, autolink: true, protocols: ["http", "https", "mailto"], HTMLAttributes: { rel: "noopener noreferrer", target: "_blank" }, }), Placeholder.configure({ placeholder }), ], editorProps: { attributes: { class: "prose prose-sm max-w-none focus:outline-none text-foreground", }, }, content: value || "", onUpdate({ editor }) { onChange?.(editor.getHTML()) }, editable: !disabled, // Avoid SSR hydration mismatches per Tiptap recommendation immediatelyRender: false, }) // Keep external value in sync when it changes useEffect(() => { if (!editor) return const current = editor.getHTML() if ((value ?? "") !== current) { editor.commands.setContent(value || "", { emitUpdate: false }) } }, [value, editor]) if (!editor) return null return (
editor.chain().focus().toggleBold().run()} active={editor.isActive("bold")} ariaLabel="Negrito" > editor.chain().focus().toggleItalic().run()} active={editor.isActive("italic")} ariaLabel="Itálico" > editor.chain().focus().toggleStrike().run()} active={editor.isActive("strike")} ariaLabel="Tachado" > editor.chain().focus().toggleBulletList().run()} active={editor.isActive("bulletList")} ariaLabel="Lista" > editor.chain().focus().toggleOrderedList().run()} active={editor.isActive("orderedList")} ariaLabel="Lista ordenada" > editor.chain().focus().toggleBlockquote().run()} active={editor.isActive("blockquote")} ariaLabel="Citação" > { const prev = editor.getAttributes("link").href as string | undefined const url = window.prompt("URL do link:", prev || "https://") if (url === null) return if (url === "") { editor.chain().focus().extendMarkRange("link").unsetLink().run() return } editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run() }} active={editor.isActive("link")} ariaLabel="Inserir link" >
editor.chain().focus().undo().run()} ariaLabel="Desfazer"> editor.chain().focus().redo().run()} ariaLabel="Refazer">
) } function ToolbarButton({ onClick, active, ariaLabel, children, }: { onClick: () => void active?: boolean ariaLabel?: string children: React.ReactNode }) { return ( ) } // Utilitário simples para renderização segura do HTML do editor. // Remove tags