106 lines
3.2 KiB
TypeScript
106 lines
3.2 KiB
TypeScript
import { useEffect } from 'react'
|
|
import { Bold, Italic, List } from 'lucide-react'
|
|
import Box from '@mui/material/Box'
|
|
import IconButton from '@mui/material/IconButton'
|
|
import Stack from '@mui/material/Stack'
|
|
import Placeholder from '@tiptap/extension-placeholder'
|
|
import { EditorContent, useEditor } from '@tiptap/react'
|
|
import TiptapStarterKit from '@tiptap/starter-kit'
|
|
|
|
type RichTextMessageEditorProps = {
|
|
value: string
|
|
onChange: (next: string) => void
|
|
placeholder?: string
|
|
disabled?: boolean
|
|
}
|
|
|
|
export function RichTextMessageEditor({
|
|
value,
|
|
onChange,
|
|
placeholder = 'Введите сообщение',
|
|
disabled = false,
|
|
}: RichTextMessageEditorProps) {
|
|
const initialContent = value.trim() ? value : '<p></p>'
|
|
|
|
const editor = useEditor({
|
|
extensions: [
|
|
TiptapStarterKit.configure({ heading: false, codeBlock: false, blockquote: false, horizontalRule: false }),
|
|
Placeholder.configure({ placeholder }),
|
|
],
|
|
content: initialContent,
|
|
editable: !disabled,
|
|
onUpdate: ({ editor: tiptap }) => {
|
|
const plainText = tiptap.getText().trim()
|
|
onChange(plainText ? tiptap.getHTML() : '')
|
|
},
|
|
})
|
|
|
|
useEffect(() => {
|
|
if (!editor) return
|
|
editor.setEditable(!disabled)
|
|
}, [disabled, editor])
|
|
|
|
useEffect(() => {
|
|
if (!editor) return
|
|
const normalizedValue = value.trim() ? value : '<p></p>'
|
|
if (editor.getHTML() === normalizedValue) return
|
|
editor.commands.setContent(normalizedValue, { emitUpdate: false })
|
|
}, [editor, value])
|
|
|
|
return (
|
|
<Box sx={{ border: 1, borderColor: 'divider', borderRadius: 2, overflow: 'hidden', bgcolor: 'background.paper' }}>
|
|
<Stack direction="row" spacing={0.5} sx={{ p: 0.75, borderBottom: 1, borderColor: 'divider' }}>
|
|
<IconButton
|
|
size="small"
|
|
onClick={() => editor?.chain().focus().toggleBold().run()}
|
|
color={editor?.isActive('bold') ? 'primary' : 'default'}
|
|
disabled={disabled}
|
|
aria-label="Жирный"
|
|
>
|
|
<Bold size={18} />
|
|
</IconButton>
|
|
<IconButton
|
|
size="small"
|
|
onClick={() => editor?.chain().focus().toggleItalic().run()}
|
|
color={editor?.isActive('italic') ? 'primary' : 'default'}
|
|
disabled={disabled}
|
|
aria-label="Курсив"
|
|
>
|
|
<Italic size={18} />
|
|
</IconButton>
|
|
<IconButton
|
|
size="small"
|
|
onClick={() => editor?.chain().focus().toggleBulletList().run()}
|
|
color={editor?.isActive('bulletList') ? 'primary' : 'default'}
|
|
disabled={disabled}
|
|
aria-label="Список"
|
|
>
|
|
<List size={18} />
|
|
</IconButton>
|
|
</Stack>
|
|
|
|
<Box
|
|
sx={{
|
|
px: 1.5,
|
|
py: 1.25,
|
|
'& .ProseMirror': {
|
|
minHeight: 72,
|
|
outline: 'none',
|
|
whiteSpace: 'pre-wrap',
|
|
wordBreak: 'break-word',
|
|
},
|
|
'& .ProseMirror p.is-editor-empty:first-of-type::before': {
|
|
content: `"${placeholder}"`,
|
|
color: 'text.disabled',
|
|
pointerEvents: 'none',
|
|
float: 'left',
|
|
height: 0,
|
|
},
|
|
}}
|
|
>
|
|
<EditorContent editor={editor} />
|
|
</Box>
|
|
</Box>
|
|
)
|
|
}
|