Интеграция редактора Tiptap в CMS сайта
Tiptap — headless rich-text редактор на базе ProseMirror. В отличие от TinyMCE и CKEditor, у него нет встроенного UI — вы строите интерфейс сами. Это даёт полный контроль над внешним видом, но требует больше работы при старте. Идеален для React-проектов с кастомным дизайном.
Установка
npm install @tiptap/react @tiptap/pm @tiptap/starter-kit
npm install @tiptap/extension-image @tiptap/extension-link @tiptap/extension-placeholder
Базовый редактор
import { useEditor, EditorContent } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import Image from '@tiptap/extension-image';
import Link from '@tiptap/extension-link';
function TiptapEditor({ content, onChange }) {
const editor = useEditor({
extensions: [
StarterKit,
Image.configure({ inline: false }),
Link.configure({ openOnClick: false })
],
content,
onUpdate: ({ editor }) => onChange(editor.getHTML())
});
if (!editor) return null;
return (
<div className="border rounded-lg">
<EditorToolbar editor={editor} />
<EditorContent editor={editor} className="prose max-w-none p-4" />
</div>
);
}
Кастомный тулбар
function EditorToolbar({ editor }) {
return (
<div className="flex gap-1 p-2 border-b">
<button
onClick={() => editor.chain().focus().toggleBold().run()}
className={editor.isActive('bold') ? 'bg-gray-200 rounded' : ''}
>
<BoldIcon className="w-4 h-4" />
</button>
<button onClick={() => editor.chain().focus().toggleItalic().run()}>
<ItalicIcon className="w-4 h-4" />
</button>
<button
onClick={() => {
const url = window.prompt('URL:');
if (url) editor.chain().focus().setLink({ href: url }).run();
}}
>
<LinkIcon className="w-4 h-4" />
</button>
{/* Кнопка загрузки изображения */}
<button onClick={() => document.getElementById('image-upload').click()}>
<ImageIcon className="w-4 h-4" />
</button>
<input
id="image-upload"
type="file"
className="hidden"
accept="image/*"
onChange={async (e) => {
const file = e.target.files[0];
const url = await uploadImage(file);
editor.chain().focus().setImage({ src: url }).run();
}}
/>
</div>
);
}
Кастомные ноды (блоки контента)
Tiptap позволяет создавать кастомные блоки — например, блок "Цитата с автором":
import { Node, mergeAttributes } from '@tiptap/core';
import { ReactNodeViewRenderer } from '@tiptap/react';
const QuoteBlock = Node.create({
name: 'quoteBlock',
group: 'block',
content: 'inline*',
addAttributes() {
return {
author: { default: null },
position: { default: null }
};
},
parseHTML() {
return [{ tag: 'blockquote[data-type="quote-block"]' }];
},
renderHTML({ HTMLAttributes }) {
return ['blockquote', mergeAttributes(HTMLAttributes, { 'data-type': 'quote-block' }), 0];
},
addNodeView() {
return ReactNodeViewRenderer(QuoteBlockComponent);
}
});
JSON vs HTML хранение
Tiptap может сохранять контент в JSON (нативный формат ProseMirror) или HTML. JSON предпочтительнее: он структурированный, не требует санитизации, легко трансформируется:
// Сохранение в JSON
const jsonContent = editor.getJSON();
// При отображении — конвертировать JSON → HTML на стороне сервера
// или использовать generateHTML из @tiptap/html
import { generateHTML } from '@tiptap/html';
const html = generateHTML(jsonContent, [StarterKit, Image, Link]);
Коллаборативное редактирование
Tiptap поддерживает совместное редактирование через Yjs:
npm install @tiptap/extension-collaboration @tiptap/extension-collaboration-cursor yjs y-websocket
Срок интеграции: 2–3 дня для полноценного редактора с тулбаром, загрузкой изображений и кастомными блоками.







