'use client'; import { useState, useCallback } from 'react'; import { useTranslations } from 'next-intl'; import { useEditor, EditorContent } from '@tiptap/react'; import StarterKit from '@tiptap/starter-kit'; import Paragraph from '@tiptap/extension-paragraph'; import Underline from '@tiptap/extension-underline'; import Link from '@tiptap/extension-link'; import TextAlign from '@tiptap/extension-text-align'; import { TextStyle } from '@tiptap/extension-text-style'; import Color from '@tiptap/extension-color'; import { useFocusTrap } from '@/hooks/use-focus-trap'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { cn } from '@/lib/utils'; import { htmlToPlainText } from '@/lib/html-to-text'; import type { Signature } from '@/stores/signature-store'; import { Bold, Italic, Underline as UnderlineIcon, Strikethrough, List, ListOrdered, AlignLeft, AlignCenter, AlignRight, Link as LinkIcon, Baseline, X, } from 'lucide-react'; interface SignatureEditorModalProps { signature?: Signature | null; onSave: (data: { name: string; body: string; plainText: string }) => void; onClose: () => void; } const StyledParagraph = Paragraph.extend({ addAttributes() { return { ...this.parent?.(), style: { default: null as string | null, parseHTML: (el: HTMLElement) => el.getAttribute('style'), renderHTML: (attrs: Record) => attrs.style ? { style: attrs.style } : {}, }, class: { default: null as string | null, parseHTML: (el: HTMLElement) => el.getAttribute('class'), renderHTML: (attrs: Record) => attrs.class ? { class: attrs.class } : {}, }, }; }, }); const TEXT_COLORS = [ '#000000', '#5f6368', '#9aa0a6', '#c5221f', '#e8710a', '#f9ab00', '#188038', '#1967d2', '#7627bb', '#c2185b', '#795548', '#fa5252', '#fd7e14', '#40c057', '#4dabf7', '#e64980', ]; function ToolbarButton({ active, onClick, children, title, disabled, }: { active?: boolean; onClick: () => void; children: React.ReactNode; title: string; disabled?: boolean; }) { return ( ); } function ToolbarSeparator() { return
; } export function SignatureEditorModal({ signature, onSave, onClose, }: SignatureEditorModalProps) { const t = useTranslations('signatures'); const tCommon = useTranslations('common'); const isEditing = !!signature; const [name, setName] = useState(signature?.name ?? ''); const [nameError, setNameError] = useState(''); const [showPreview, setShowPreview] = useState(false); const [colorMenuOpen, setColorMenuOpen] = useState(false); const dialogRef = useFocusTrap({ isActive: true, onEscape: onClose, restoreFocus: true, }); const editor = useEditor({ extensions: [ StarterKit.configure({ heading: false, paragraph: false, link: false, underline: false, codeBlock: false, }), StyledParagraph, Underline, Link.configure({ openOnClick: false, HTMLAttributes: { rel: 'noopener noreferrer nofollow' }, }), TextAlign.configure({ types: ['paragraph'], }), TextStyle, Color, ], content: signature?.body ?? '

', editorProps: { attributes: { class: 'tiptap min-h-[120px] px-3 py-2 text-sm text-foreground focus:outline-none', }, }, immediatelyRender: false, }); const addLink = useCallback(() => { if (!editor) return; const previousUrl = editor.getAttributes('link').href; const url = window.prompt('URL', previousUrl); if (url === null) return; if (url === '') { editor.chain().focus().extendMarkRange('link').unsetLink().run(); return; } editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run(); }, [editor]); const handleSave = () => { const trimmedName = name.trim(); if (!trimmedName) { setNameError(t('name_required')); return; } const html = editor?.getHTML() ?? '

'; const plainText = htmlToPlainText(html); onSave({ name: trimmedName, body: html, plainText }); }; const bodyHtml = editor?.getHTML() ?? ''; const bodyPlainText = htmlToPlainText(bodyHtml); return (

{isEditing ? t('edit_signature') : t('new_signature')}

{ setName(e.target.value); if (nameError) setNameError(''); }} placeholder={t('name_placeholder')} className={cn(nameError && 'border-destructive')} aria-invalid={!!nameError} aria-describedby={nameError ? 'sig-name-error' : undefined} /> {nameError && ( )}
{t('editor_label')}
{showPreview ? (
{t('html_preview_label')}
{t('plain_text_preview_label')}
                    {bodyPlainText}
                  
) : (
editor?.chain().focus().toggleBold().run()} title={t('toolbar.bold')} > editor?.chain().focus().toggleItalic().run()} title={t('toolbar.italic')} > editor?.chain().focus().toggleUnderline().run()} title={t('toolbar.underline')} > editor?.chain().focus().toggleStrike().run()} title={t('toolbar.strikethrough')} >
setColorMenuOpen((v) => !v)} title={t('toolbar.text_color')} > {colorMenuOpen && (
{TEXT_COLORS.map((color) => (
)}
editor?.chain().focus().toggleBulletList().run()} title={t('toolbar.bullet_list')} > editor?.chain().focus().toggleOrderedList().run()} title={t('toolbar.ordered_list')} > editor?.chain().focus().setTextAlign('left').run()} title={t('toolbar.align_left')} > editor?.chain().focus().setTextAlign('center').run()} title={t('toolbar.align_center')} > editor?.chain().focus().setTextAlign('right').run()} title={t('toolbar.align_right')} >
)}
); }