russ_react/src/components/ContentEditor.tsx

139 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, forwardRef, useImperativeHandle } from 'react';
import { useEditor, EditorContent, Editor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import TextAlign from '@tiptap/extension-text-align';
import Image from '@tiptap/extension-image';
import Highlight from '@tiptap/extension-highlight';
import Blockquote from "@tiptap/extension-blockquote";
export interface ContentEditorRef {
setContent: (content: string) => void;
getEditor: () => Editor | null;
}
const ContentEditor = forwardRef<ContentEditorRef>((_, ref) => {
const [selectedImage, setSelectedImage] = useState<string | null>(null);
const ResizableImage = Image.extend({
addAttributes() {
return {
...this.parent?.(),
src: {
default: null,
},
alt: {
default: null,
},
title: {
default: null,
},
class: {
default: 'max-w-full h-auto', // Добавлено для адаптивности
},
width: {
default: 'auto',
parseHTML: (element) => element.getAttribute('width') || 'auto',
renderHTML: (attributes) => ({
width: attributes.width,
}),
},
height: {
default: 'auto',
parseHTML: (element) => element.getAttribute('height') || 'auto',
renderHTML: (attributes) => ({
height: attributes.height,
}),
},
};
},
});
const editor = useEditor({
extensions: [
StarterKit.configure({
blockquote: false, // Отключаем дефолтный
}),
Blockquote.configure({
HTMLAttributes: {
class: 'border-l-4 border-gray-300 pl-4 italic',
},
}),
TextAlign.configure({
types: ['heading', 'paragraph'],
}),
ResizableImage,
Highlight,
],
content: '',
onUpdate: ({ editor }) => {
const pos = editor.state.selection.$anchor.pos;
const resolvedPos = editor.state.doc.resolve(pos);
const parentNode = resolvedPos.parent;
if (parentNode.type.name === 'image') {
setSelectedImage(parentNode.attrs.src); // Сохраняем URL изображения
} else {
setSelectedImage(null); // Очищаем выбор, если это не изображение
}
},
editorProps: {
attributes: {
class: 'prose prose-lg focus:outline-none min-h-[300px] max-w-none',
},
},
});
// Позволяем родительскому компоненту использовать методы редактора
useImperativeHandle(ref, () => ({
setContent: (content: string) => {
editor?.commands.setContent(content);
},
getEditor: () => editor,
}));
// Функция изменения размера изображения
const updateImageSize = (delta: number) => {
if (selectedImage) {
const attrs = editor?.getAttributes('image');
const newWidth = Math.max((parseInt(attrs?.width) || 100) + delta, 50);
editor?.chain().focus().updateAttributes('image', { width: `${newWidth}px` }).run();
}
};
return (
<div className="relative">
<EditorContent editor={editor} />
{selectedImage && (
<div
className="absolute z-10 flex space-x-2 bg-white border p-1 rounded shadow-lg"
style={{
top: editor?.view?.dom
? `${editor.view.dom.getBoundingClientRect().top + window.scrollY + 50}px`
: '0px',
left: editor?.view?.dom
? `${editor.view.dom.getBoundingClientRect().left + window.scrollX + 200}px`
: '0px',
}}
>
<button
onClick={() => updateImageSize(-20)}
className="px-2 py-1 bg-gray-200 rounded hover:bg-gray-300"
>
Уменьшить
</button>
<button
onClick={() => updateImageSize(20)}
className="px-2 py-1 bg-gray-200 rounded hover:bg-gray-300"
>
Увеличить
</button>
</div>
)}
</div>
);
});
ContentEditor.displayName = 'ContentEditor';
export default ContentEditor;