Наша задача — внедрить красивый и надежный текстовый редактор в 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>
.
Мы рассмотрели основные шаги по реализации редактора форматированного текста. Надеемся, что эта статья была полезна для вас. Спасибо за прочтение!
Читайте также:
- Лучший способ для привязки обработчиков событий в React
- React hooks: никакой магии, только массивы
- Насколько хорошо вы знаете React? (тест)
Читайте нас в Telegram, VK и Дзен
Перевод статьи Bianca Dragomir: How To Implement a Rich Text Editor in Your React App