Как реализовать редактор форматированного текста в вашем React-приложении

Наша задача  —  внедрить красивый и надежный текстовый редактор в React-приложение. Он позволит пользователю писать статьи/рассказы так же, как если бы он это делал в Medium, Google Docs или любом другом редакторе, который поддерживает следующие функции:

  • блочное форматирование (обычный текст, заголовки H1, H2 и т. д.);
  • строчное форматирование (жирный шрифт, подчеркивание, курсив и т. д.);
  • вставку ссылок и изображений или даже упорядоченных и неупорядоченных списков элементов.

Мы также увидим, как можно получить доступ к тексту, который вставил пользователь, вместе с примененным форматированием, в виде HTML.


Мы будем использовать библиотеку, в которую встроен фреймворк DraftJS, разработанный компанией Facebook. Говоря простым языком, DraftJS  —  это “фреймворк для редактора форматированного текста на React” (из официальной документации). Чтобы упростить реализацию редактора с помощью этого фреймворка, можно воспользоваться оберточной библиотекой: react-draft-wysiwyg.

Wysiwyg означает What you see is what you get (“Что видишь, то и получаешь”), и это идеальное описание для подобного редактора. Пользователь применяет к тексту определенное форматирование и таким образом определяет, как будет выглядеть его статья/рассказ впоследствии (например, когда текст будет опубликован на сайте).


Сначала нужно добавить необходимые зависимости:

$ npm install --save react-draft-wysiwyg draft-js

Поскольку эта библиотека включает в себя вышеупомянутый фреймворк, нужно добавить и то, и другое в качестве зависимостей в проект.

Приступить к работе и добавить компонент Editor в проект очень просто:

import { Editor } from "react-draft-wysiwyg";
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";

...
<Editor
  editorState={editorState}
  onEditorStateChange={this.onEditorStateChange}
/>;
...

Не забудьте про импорт CSS  —  в противном случае у вас не будет применен стиль по умолчанию, и вы потратите 15 минут, пытаясь понять, из-за чего возникла проблема

В приведенном выше фрагменте мы передаем два обязательных свойства, чтобы редактор можно было использовать. Объект editorState  —  это EditorState, который содержит (как указано в документации):

  • текущее состояние содержимого текста;
  • текущее состояние выбора;
  • полностью оформленное представление содержимого;
  • стеки отмены/повтора;
  • последний тип изменений, внесенных в содержимое.

Для преобразования внутреннего состояния редактора в HTML понадобится другая библиотека. Мы обсудим это ниже.


Разумеется, нам нужно красивое, современное веб-приложение, а интерфейсу по умолчанию нужны доработки. Не волнуйтесь  —  сейчас мы рассмотрим план действий по улучшению редактора.

Стандартный вид редактора

Вот что нужно сделать.

  • Полностью изменить внешний вид панели инструментов.
  • Применить пользовательские иконки.
  • Разделить опции, которые видят пользователи настольных ПК и пользователи мобильных устройств.
  • Стилизовать выпадающие и всплывающие окна.
Так выглядит редактор после стилизации

Ввод данных с приведенного выше скриншота будет выглядеть следующим образом:

<h1>This is a heading</h1><p>Hello everyone! Let’s edit some text.</p>

Прежде всего, посмотрим, как настроить параметры. Добавляем свойство toolbar в компонент Editor. Для этого используйте следующий код:

<Editor
toolbar={{
// Здесь добавляем конфигурации
}}
...
/>;

В документации все описано довольно подробно, но не хватает некоторых пунктов. Вот несколько конкретных советов по работе с этим редактором.

Сначала посмотрим, как можно настроить встроенную стилизацию редактора (какие стили можно применить к тексту) и как отобразить внешний вид этих опций в пользовательском интерфейсе.

inline: {
inDropdown: false,
options: ['bold', 'underline', 'italic'],
bold: { icon: FormatBoldIcon },
italic: { icon: FormatItalicIcon },
underline: { icon: FormatUnderlineIcon },
},
  • Кроме полужирного шрифта, курсива и подчеркивания, вы также можете использовать зачеркнутый шрифт, моноширинный шрифт, надстрочные и подстрочные знаки.
  • Вы можете отображать опции в выпадающем списке или распределить их по панели инструментов.
  • Для каждой опции встроенного редактирования вы также можете предоставить собственную иконку SVG.
  • Иконка будет импортирована в таком виде:
import FormatBoldIcon from './format_bold_icon.svg';

Вы можете также настроить другие поля объекта панели инструментов:

blockType: { // текст, H1, H2 и т. д.
...
},
list: { // упорядоченные или неупорядоченные списки
...
},
link: { // добавление URL-адреса с пользовательским текстом
...
},
image: { // для вставки изображения в редактор
...
}

В документации эти опции даны вместе с объяснениями, поэтому не будем останавливаться на них подробно. Настройка каждой из них аналогична настройке опции inline.

Теперь рассмотрим более сложный вариант. Иногда при вставке изображений можно столкнуться с проблемами.

image: {
urlEnabled: false,
uploadEnabled: true,
icon: InsertPhoto,
uploadCallback: uploadCallback,
previewImage: true,
inputAccept: 'image/jpeg,image/jpg,image/png',
alignmentEnabled: false,
defaultSize: {
height: auto,
width: auto,
},
},

Очень важно установить параметр previewImage на true. В противном случае во всплывающем окне, где вы выбрали изображение, будет отображаться URL, а не само изображение. Скорее всего, с остальными опциями вы легко разберетесь. А как насчет uploadCallback?

const uploadCallback = async (file: any) => {
const formData = new FormData();
formData.append('file', file);
const uploadedImageUrl = await uploadImageCall(formData);

return new Promise((resolve) => {
resolve({
data: {
link: uploadedImageUrl,
},
});
});
};

Самый важный момент тут  —  вернуть промис, обратный вызов resolve которого вернет URL-адрес изображения, указанного в поле link.

В конце туториала, с целью сохранения чистоты и детализации демонстрации, рассмотрим, как получить HTML-эквивалент состояния ввода.

Для этого потребуется добавить другую библиотеку. Здесь есть несколько вариантов. Воспользуемся draft-convert. Метод convertToHtml вернет то, что было введено в редактор в виде HTML.

useEffect(() => {
const html = convertToHTML(editorState.getCurrentContent());
props.setContentHtml(html);
}, [editorState]);

Здесь editorState инстанцируется следующим образом:

const [editorState, setEditorState] = useState<EditorState>(
EditorState.createEmpty()
);

Мы также можем настроить HTML-вывод метода convertToHtml с помощью следующего кода:

useEffect(() => {
const html = convertToHTML({
entityToHTML: (entity, originalText) => {
if (entity.type === 'IMAGE') {
return {
start: `<img src='${entity.data['src']}'>`,
end: '</img>',
empty: '',
};
}
return originalText;
},
})(editorState.getCurrentContent());
}, [editorState]);

Выше используется переопределение, потому что в библиотеке при вставке изображения выводилось <figure> </figure> и больше ничего! Теперь вывод правильный  —  <img src="..."></img>.


Мы рассмотрели основные шаги по реализации редактора форматированного текста. Надеемся, что эта статья была полезна для вас. Спасибо за прочтение!

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Bianca Dragomir: How To Implement a Rich Text Editor in Your React App

Предыдущая статьяУлучшение визуализации данных с помощью двухосевых диаграмм в Python
Следующая статьяКонструкция контейнера в Docker