Подготовка
Начнём с подключения библиотеки TinyMCE и базовой инициализации редактора. Библиотеку можно скачать с официального сайта, подключить из CDN или установить с помощью пакетного менеджера NPM. Кроме того, можно использовать облачную версию. Все способы подключения можно изучить в официальной документации.
В HTML необходимо определить элемент textarea
, который будет нашим редактором, а также подключить скрипт TinyMCE и свой скрипт. Результат будет следующим:
<textarea id="editor"></textarea>
<script src="./tinymce.min.js"></script>
<script src="./main.js" type="module"></script>
Теперь создадим файл alerts.js
и напишем в нём минимальный код будущего плагина. Плагин представляем собой функцию обратного вызова, которую можно зарегистрировать в PluginManager. В функцию параметром передается объект текущего редактора. В плагине определим кнопку для панели и команду, которую она будет выполнять.
export const AlertsPlugin = editor => {
// Выполняемая команда
editor.addCommand('edit_alert', () => {
// ...
});
// Кнопка с идентификатором 'alerts'
editor.ui.registry.addToggleButton('alerts', {
icon : 'warning',
text : 'Alert',
onAction: () => editor.execCommand('edit_alert'),
});
};
И в main.js
зарегистрируем наш плагин и выполним настройку редактора. Не будем усложнять тестовый пример, просто добавим несколько простых кнопок на тулбар.
import { AlertsPlugin } from './alerts.js';
// Добавим плагин в tinymce с идентификатором 'alerts'
tinymce.PluginManager.add('alerts', AlertsPlugin);
tinymce.init({
target: document.getElementById('editor'),
// Укажем идентификатор плагина, чтобы редактор его начал использовать
plugins: ['code', 'alerts'],
// Укажем идентификатор кнопки, чтобы редактор её создал
toolbar : 'code | h2 h3 | bold italic underline | alerts',
skin : 'tinymce-5-dark',
menubar : false,
branding: false,
height : 500,
});
Также, давайте сразу определим внешний вид блока сообщения внутри редактора, потому что обычные дивы никак не выделяются в потоке текста и мы просто не сможем визуально их отличить от обычных параграфов.
Чтобы изменить стилизацию контента в редакторе, нужно подключить к нему таблицу стилей где и описать внешний вид контента. Для этого создадим css-файл, например tinymce-content.css
и укажем url-путь к нему в конфигурации редактора. Обратите внимание, что стили, написанные в этом файле, влияют только на окно редактора. Как будет выглядеть этот контент при выводе на сайте, будет зависеть от стилевых таблиц сайта.
tinymce.init({
// ...
content_css: './tinymce-content.css',
});
Каким образом стилизовать подобные блоки, полностью зависит от вашей фантазии, мы же для примера ограничимся простыми стилями.
.alert {
padding: 0 1rem;
border: 2px solid #ccc;
border-left-width: 1rem;
}
.alert-info { border-color: dodgerblue; }
.alert-success { border-color: lawngreen; }
.alert-warning { border-color: chocolate }
.alert-danger { border-color: crimson }
Это даст нам такое отображение блока в редакторе:
Реализация плагина
Сейчас у нас есть кнопка на панели инструментов которая ничего не делает. Исправим это. Кнопка должна открывать окно настроек для алерта. Данный диалог будет содержать единственую опцию – тип сообщения, и две кнопки - отмена и подтвержение. В случае подтверждения в поле редактора должен будет вставиться новый блок сообщения с выбранным типом, либо измениться тип уже существующего сообщения. Я решил сделать пять типов сообщений: default, info, success, warning, danger.
Для отображения диалога воспользуемся интерфейсом tinymce.WindowManager. Его метод .open()
позволяет создать и открыть окно с требуемым набором элементов управления и вода. Оформим это в виде отдельной функции.
const types = [
{ text: 'Простой текст (default)', value: 'default' },
{ text: 'Информация (info)', value: 'info' },
{ text: 'Уведомление (success)', value: 'success' },
{ text: 'Предупреждение (warning)', value: 'warning' },
{ text: 'Важная информация (danger)', value: 'danger' },
];
const openDialog = editor => {
editor.windowManager.open({
// Заголовок диалога
title: 'Информационное сообщение',
// Содержимое диалога
body: {
type: 'panel',
// Список элементов
items: [
{
type : 'listbox', // Выпадающий список
name : 'type', // Имя для доступа из кода
label: 'Тип сообщения',// Подпись
items: types, // Список значений в списке
},
],
},
// Набор кнопок в диалоге
buttons: [
{
type: 'cancel',
name: 'cancel',
text: 'Cancel',
},
{
type : 'submit',
name : 'save',
text : 'Save',
primary: true,
},
],
});
};
И поместим вызов функции в команду edit_alert
.
export const AlertsPlugin = editor => {
// Выполняемая команда
editor.addCommand('edit_alert', () => {
// Открываем диалог настроек блока сообщения
openDialog(editor);
});
// ...
};
Чтобы блок появился или обновился в редакторе, нужно добавить обработчик кнопки Save в диалоговом окне настроек. Функция обратного вызова принимает первым параметром объект Api, позволяющий получить введённые в диалоге данные. В данном случае нас интересует поле data.type
, содержащее тип сообщения.
const openDialog = editor => {
editor.windowManager.open({
// ...
// Обработчик сохранения
onSubmit: api => {
const data = api.getData();
insertAlertBlock(editor, data.type);
api.close();
},
});
};
Функция вставки/обновления довольна проста. Нужно получить узел под курсором и определить, являемся ли мы внутри существующего блока сообщения или находимся в тексте. В первом случае мы просто обновим css-класс у блока, а во втором – вставим в позицию курсора новый блок. Если мы создаем новый блок сообщения, то дополнительно проверим, есть в редакторе выделенный текст. Если таковой присутствует, то мы поместим его во внутрь созданного сообщения.
const getSelectedAlertBlock = editor =>
editor.selection.getNode().closest('.alert');
const getSelectedContent = editor => {
// Получаем выделенный текст
const selectedContent = editor.selection.getContent().trim();
return selectedContent.length && selectedContent.startsWith('<')
? selectedContent
: `<p>${selectedContent}</p>`;
};
const insertAlertBlock = (editor, type) => {
const node = getSelectedAlertBlock(editor);
if (node) {
// Если курсор внутри блока, то просто меняем его тип
node.className = `alert alert-${type}`;
} else {
// Если курсор вне блока, то вставляем новый
editor.insertContent(`<div class="alert alert-${type}">${getSelectedContent(editor)}</div>`);
}
};
Для того, чтобы диалог настроек корректно отображал тип существующего блока добавим соответствующий код в функцию его открытия.
const openDialog = editor => {
const alert = editor.selection.getNode().closest('.alert');
let currentType = 'default';
let matches = /alert-(default|info|success|warning|danger)/.exec(alert?.className);
if (alert && matches) currentType = matches[1];
editor.windowManager.open({
// ...
// Данные для передачи в диалог
initialData: {
type: currentType,
},
});
};
Остался последний важный момент. Если мы создадим блок последним в документе, то после него не получится вставить курсор и продолжить писать статью. Я, честно говоря, не смог разобраться как правильным образом разрешить данную ситуацию и буду рад подсказкам. Но так как проблему всё равно нужно решить, то немножко поговнокодим =).
Решение будет заключаться в следующем. Повесим слушателя на нажатие клавиши ECS
или сочетания CTRL+ENTER
. Так как слушатель вешается на весь редактор, то следует проверить, находится ли курсор внутри блока сообщения и только в этом случае выполнять следующие действия. Основная логика заключается в том, чтобы выяснить, является ли блок последним в документе. Если да, то нужно сгенерировать пустой параграф и вставить его после блока, а если нет, то просто установить курсор в следующий элемент.
export const AlertsPlugin = editor => {
// ...
editor.on('keydown', function (e) {
if (e.keyCode === 27 || (e.keyCode === 13 && e.ctrlKey)) {
const alertBlock = editor.selection ? editor.selection.getNode().closest('.alert') : null;
if (alertBlock) {
e.preventDefault();
const container = alertBlock.parentNode;
const isLast = alertBlock === container.lastChild;
let nextElement = alertBlock.nextElementSibling;
if (isLast) {
nextElement = editor.dom.create('p');
nextElement.innerHTML = '<br data-mce-bogus>';
editor.dom.insertAfter(nextElement, alertBlock);
}
const rng = editor.dom.createRng();
rng.setStart(nextElement, 0);
rng.setEnd(nextElement, 0);
editor.selection.setRng(rng);
}
}
});
};
В результате мы получили возможность вставлять в текст вот такие блоки.
Пример редактора с новым плагином можно посмотреть здесь – https://delphinpro.ru/examples/tinymce/alerts-plugin.
Полный код размещен в репозитории на гитхабе – https://github.com/delphinpro/mce-alert-plugin
Вы можете оставить комментарий: