Богатый текстовый редактор (БТР)

Введение

Богатый текстовый редактор (БТР) — элемент графического интерфейса пользователя, предназначенный для отображения и ввода форматированного текста. В Wasaby для работы с форматированным текстом применяется собственный формат JSON для представления HTML разметки — JsonML Richtext.

Для редактирования текста в формате JsonML используется контрол RichEditor/base:Editor, а для работы с панелью редактирования — RichEditor/extended:Editor.

БТР состоит из следующих элементов:

  • RichEditor/extended:Controller служит для связывания панели редактирования и поля ввода.
  • Панель инструментов (тулбар). В библиотеке контролов представлен как RichEditor/extended:Toolbar.
  • Поле ввода, которое реализовано с помощью двух контролов:
    • RichEditor/base:Editor — поле ввода для форматированного текста;
    • RichEditor/base:Viewer — для отображения форматированного текста в режиме чтения.

Рис. 1. Пример использования БТР при создании новости на online.sbis.ru

Рис. 2. Развернутый список команд БТР

Текстовый редактор обладает расширенным функционалом форматирования, редактирования и вставки текстов, а также внедрения в него таблиц, изображений и цитат кода.

В основе контрола лежит сторонняя библиотека TinyMCE с набором плагинов.

Демонстрационный пример

Посмотрите наш интерактивный пример с контролом "Богатый текстовый редактор", а также ознакомьтесь с его исходными файлами.

Справочные материалы и ресурсы

Руководство разработчика по конфигурации контрола

Для конфигурации БТР предоставлены следующие опции:

  • Опции полей ввода:
    • tagStyle — цвет рамки поля ввода.
    • Style — стиль текста.
    • tooltip — всплывающая подсказка.
    • selectOnClick — выделять текст в поле ввода по клику.
  • Опции форматирования:
    • textFormats — список предустановленных стилей для текста. Текстовые форматы могут применяться посредством метода applyFormats().

Базовые форматы описаны в RichEditor/base:contstants.tinyConstants.styles.

Пример задания набора пользовательских форматов:

import {constants} from 'RichEditor/base';
  
const textFormats = {
    ...constants.tinyConstants.styles,
    accented: {
        inline: 'span',
        classes: 'news_Panel_accentedText',
        title: 'Акцентированный'
    },
    dull: {
        inline: 'span',
        classes: 'news_Panel_dullText',
        title: 'Тусклый'
    },
    bigHeading: {
        block: 'h1',
        classes: 'news_Panel_bigHeadingText',
        title: 'Большой заголовок'
    }
};

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

  • Опции для загрузки изображения:
    • imageUploader — служит для загрузки и редактирования изображений. Если данная опция не указана, кнопка вставки изображения не отобразится. Загрузчик представляет собой объект с двумя методами:
      • getFiles — обязательный метод, необходимый для загрузки изображений. Должен возвращать Promise<IImageFile[]> и иметь два параметра:
        • target - HTMLElement, служащий для позиционирования открываемого окна;
        • opener - Контрол, являющийся опенером.
      • editImage — метод, используемый для редактирования изображения. Если он не реализован, то кнопка редактирования не появится в панели управления изображением. Также editImage должен возвращать Promise<IImageData> и иметь два параметра:
        • data - Объект метаданных изображения, реализующий IImageData;
        • opener - Контрол, являющийся опенером.
  • Опции для настройки вставки:
    • pasteConfig — конфигурация вставки имеет 3 параметра:
      • replaceYoutubeLinks — вставить ссылку в виде медиа-объекта;
      • allowImages — разрешить вставлять изображение;
      • allowedClasses — список разрешенных css-классов.
  • Опции для настройки стиля текста: * fontSizes — список разрешенных шрифтов, автоматически добавляемый в стилевую панель; * fontColors — список разрешенных цветов, автоматически добавляемый в палитру и стилевую панель.

Конфигурация набора элементов для тулбара

Конфигурирование через набор стандартных кнопок

Набор элементов и для панели редактирования устанавливается с помощью опции toolbarItems. В опцию передается массив с объектным описанием кнопок. Набор стандартных элементов панели редактирования находится в ButtonsConstants из библиотеки RichEditor/extended.

Рассмотрим пример конфигурирования тулбара.

  • TypeScript
    import {Control} from 'UI/Base';
    import {ButtonsConstants} from 'RichEditor/extended';
     
    MyControl extends Control {
        private value: JsonML;
        private toolbarItems: object[];
         
        protected _beforeMount() {
            this.toolbarItems = [ButtonsConstants.EMOJI, ButtonsConstants.CODESAMPLE];
        }
    }
  • WML
    <RichEditor.extended:Editor bind:value="value" toolbarItems="{{toolbarItems}}"/>

В данном примере добавили кнопки emoji и codesample.

Создание собственных кнопок для панели редактирования

Для частных случаев предусмотрено создание прикладных кнопок для панели редактирования.

Рассмотрим на примере кнопки выделения информационным блоком:

В опции кнопок панели редактирования приходит editorModel — событийная модель, содержащая текущее состояние редактора, с помощью которой можно узнать о выделении (selection) и форматировании в редакторе.

Также, чтобы вызвать метод из API редактора, необходимо нотифицировать событие callMethod с именем метода (например, applyFormats,removeFormats и т.д.) в качестве первого параметра и аргументами в качестве остальных.

  • TypeScript
    import * as Control from 'Core/Control';
    import * as template from 'wml!News/RTEButtons/InfoBlock';
    import {RecordSet} from 'Types/collection';
    import {IObservableObject} from 'Types/entity';
     
    interface IOptions = {
        editorModel: IObservableObject
    };
     
    export default class InfoBlock extends Control<IOptions> {
        private isActive: boolean = false;
        private icons: string[] = ['icon-Info icon-medium'];
        protected _template: Function = template;
         
        protected _beforeMount({editorModel}): void {
            editorModel.subscribe('formatsChanged', this.formatsChangedHandler, this);
        }
         
        protected _beforeUnmount(): void {
            this._options.editorModel.unsubscribe('formatsChanged', this.formatsChangedHandler, this);
        }
         
        protected formatsChangedHandler(formats: RecordSet): void {
            this.isActive = formats.get('infoBlock').get('state');
        }
         
        protected clickHandler(): void {
            if (this.isActive) {
                this._notify('callMethod', ['removeFormats', ['infoBlock']], {bubbling: true});
            } else {
                 
                // Инфоблок несовместим с форматом цитаты
                this._notify('callMethod', ['removeFormats', ['blockquote']], {bubbling: true});
                this._notify('callMethod', ['applyFormats', [{formatName: 'infoBlock', state: true}]], {bubbling: true});
            }
        }
    }
  • WML
    <Controls.toggle:Button
        value="{{isActive}}"
        inlineHeight="l"
        viewMode="pushButton"
        icons="{{icons}}"
        iconStyle="secondary"
        on:valueChanged="clickHandler()"/>

Для того чтобы созданная кнопка попала в панель редактирования, передайте ее в опцию toolbarItems:

MyControl extends Control {
    ...
    protected _beforeMount() {
        this.toolbarItems = [id:'InfoBlock', template:'News/RTEButtons/InfoBlock'];
    }
}
  • id — ключ (уникальный идентификатор) кнопки;
  • template — путь до контрола.

Если при активации кнопки нужно использовать всплывающие окна Controls.popup:Sticky, то обязательно при открытии проставлять опцию actionOnScroll="close" (см. опции).

Реализация прикладного БТР

Библиотека предоставляет набор контролов для реализации БТР со своим тулбаром:

  • RichEditor/extended:Toolbar — это панель, на которой размещены кнопки, каждая из которых имеет определенный функционал.
  • RichEditor/extended:Controller — это контроллер, который связывает панель редактирования с полем ввода текста. Для построения панели редактирования используйте контентную опцию toolbar, а для поля ввода — input.

Далее рассмотрим пример конфигурирования собственной кнопки.

  • WML
    <RichEditor.extended:Controller bind:value="value" attr:class="ws-flexbox ws-flex-row-reverse">
        <ws:toolbar>
            <RichEditor.extended:Toolbar items="{{items}}"/>
        </ws:toolbar>
        <ws:input>
            <RichEditor.base:Editor/>
        </ws:input>
    </RichEditor.extended:Controller>
  • TypeScript
    import {ButtonsConstants} from 'RichEditor/Extended';
    class MyControl extends Control {
        private items: Array<HashMap<any>>
        protected _beforeMount() {
            this.items = [ButtonsConstants.EMOJI, ButtonsConstants.CODESAMPLE];
        }
    }

Чтобы кнопки могли реагировать на изменения в редакторе (форматирование, выделение), необходимо в опцию model передать модель, которая предоставляется контролом RichEditor/extended:Controller.

<ws:toolbar>
    ...
    <RichEditor.extended:Toolbar
        ...
        model="{{toolbar.model}}"/>
    ...
</ws:toolbar>

Валидация

Валидация производится аналогично полям ввода.

Используйте стандартные валидаторы:

  • RichEditor/validators:maxLength — валидация максимальной длины;
  • RichEditor/validators:isRequired — проверка на отсутствие значения.

События ввода данных

События ввода данных БТР аналогичны событиям полей ввода.

Редактирование по месту

Редактирование по месту производится аналогично полям ввода.

Оптимизация размеров загружаемых изображений

Опция imageUrlResolver вызывается при вставке/изменении размеров изображения и служит для предобработки размеров загруженных изображений. Её следует использовать для подгонки размеров изображения под реальные размеры редактора, учитывая дисплеи с повышенной плотностью пикселей. Базовый резолвер, который предобрабатывает размеры через сервис Previewer, доступен в Previewer/UrlHelper:getUrlOfResizedImage.

  • WML
    <RichEditor.extended:Editor imageUrlResolver="{{_imageUrlResolver}}"/>
  • TS
    import {getUrlOfResizedImage} from 'Previewer/UrlHelper';
    export default class MyControl extends Control<...> {
        protected _imageUrlResolver: Function = getUrlOfResizedImage;
        ...
    }