Перейти к основному содержимому
Версия: Next

Создание надстроек

Что такое надстройка для редактора, какие есть ограничения среды исполнения надстроек, а также специфика управления внешними ресурсами описано в статье: Надстройки для редактора.

Полноценные надстройки представляют из себя проекты, код которых может быть реализован с использованием технологий Typescript, CoffeeScript, Dart, Elm и других. Главное, чтобы была возможность скомпилировать весь код в один JavaScript-файл надстройки на выходе.

Нередко надстройки импортируют и сторонние библиотеки для реализации своих возможностей.

В этой статье будет рассмотрен пример создания демонстрационного надстройки на TypeScript с подключением сторонней библиотеки. В качестве сборщика (бандлера) будем использовать Vite.

Мы создадим проект для надстройки и шаг за шагом рассмотрим возможности EditorApi для реализации возможных задач.

demo-plugin.zip

Вы можете скачать и ознакомиться к кодом демонстрационной надстройки, перейдя по ссылке: Скачать архив demo-plugin.zip

Создание проекта для надстройки

Сперва создадим пустой проект для надстройки:

demo-plugin/
├── src/
│ └── index.ts
├── package.json
├── tsconfig.json
└── vite.config.ts
export default {
onInit() {
console.log('Поздравляем! Это ваша первая надстройка');
},
};

Теперь у нас есть песочница для создания надстройки.

Соберем проект и убедимся, что в выходной папке появится файл с надстройкой dist / demo-plugin.js

npm run build

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

Интеграция элементов управления надстройки

Для взаимодействия с пользователем привычным способом надстройка имеет возможность проинтегрировать свои элементы управления в системы редактора:

  • панель инструментов
  • контекстное меню
  • боковая панель

Надстройка будет получать управление, когда пользователь взаимодействует с этими элементами посредством функций обратного вызова (callback).

Для любого взаимодействия с редактором надстройка должна использовать глобальную переменную editorApi. Ее интерфейс структурирован на модули для работы с разными подсистемами. В частности, для регистрации пользовательских элементов управления используется модуль editorApi.ui

Использование панели инструментов

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

Реализуем это в новом файле src/ribbon.ts и обратимся к возможностям editorApi.ui.ribbon.

demo-plugin/
└── src/
└── ribbon.ts
import type { Button } from '@nct/web-editor-api';

const callbacks = {
// Все функции обратного вызова должны создаваться с помощью editorApi.createCallback()
onAbout: editorApi.createCallback(showAbout),
};

export function registerRibbonElements() {
const aboutButton: Button = {
id: 'demo-plugin:ribbon:button:about',
type: 'button',
title: 'О надстройке',
icon: 'Question',
// Зарегистрируем обработчик нажатия кнопки
onClick: callbacks.onAbout,
};

// Зарегистрируем в панели инструментов отдельную вкладку с названием "Демо надстройка"
editorApi.ui.ribbon.addTab({
id: 'demo-plugin',
title: 'Демо надстройка',
groups: [{
id: 'demo-plugin:ribbon:group',
controls: [aboutButton]
}]
})
};

// Снова используем editorApi.ui для показа модального окна со справкой по надстройке
function showAbout() {
editorApi.ui.modals.showModal({
id: 'plugin-demo:modal:about',
title: 'О Демо надстройке',
content: 'Демонстрационная надстройка...'
})
}

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

Подробнее с возможностями метода editorApi.ui.ribbon.addTab() можно познакомиться в статье Настройка панели инструментов или в описании API editorApi.ui.ribbon: RibbonApi:

  • вы можете создать собственные вкладки на панели инструментов для размещения элементов управления,
  • вы можете зарегистрировать группы элементов управления на вкладке "Надстройки",
  • можно регистрировать отдельные кнопки, а можно объединить действия в кнопки с выпадающими списками,
  • любые обработчики нужно оборачивать в editorApi.createCallback()

Другим удобным способом показать кнопки надстройки, которые зависят от контекста в редакторе, является контекстное меню.

Использование контекстного меню

Так же, как и панель инструментов, контекстное меню поддерживает простые кнопки или кнопки с выпадающими списками кнопок.

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

Давайте зарегистрируем пункт контекстного меню, который будет показан в случае, если контекстное меню вызвано для выделенного текста в документе.

Создадим файл src/context-menu.ts и обратимся к возможностям editorApi.ui.contextMenu.

demo-plugin/
└── src/
└── context-menu.ts
import type { ContextMenuItem } from '@nct/web-editor-api';

export function registerContextMenuItems() {
const processTextItem: ContextMenuItem = {
id: 'demo-plugin:context-menu:process-text',
title: 'Обработать текст',
onClick: editorApi.createCallback(() => console.log('Выбран пункт меню')),
// Ограничим список контекстов, в которых будет показан этот пункт: выделение внутри текста
shownInScopes: [{ mode: 'selection', scope: 'text' }],
};

// Зарегистрируем этот пункт
editorApi.ui.contextMenu.addItems([processTextItem]);
};

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

Подробнее о контекстах в редакторе, доступных пунктам меню, можно ознакомиться в статье: Настройка контекстного меню или в описании API ContextMenuApi.

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

Для таких задач предусмотрена интеграция в боковую панель редактора для размещения собственных вкладок.

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

Создадим для этого файл src / sidebar.ts и обратимся к возможностям editorApi.ui.sidebar.

demo-plugin/
└── src/
└── sidebar.ts
import type { CustomSidebarContentPart, Textblock, Button } from '@nct/web-editor-api';

export function registerSidebarElements() {
// Зарегистрируем отдельную вкладку в боковой панели с названием "Демо надстройка"
editorApi.ui.sidebar.addTab({
id: 'demo-plugin:sidebar',
title: 'Демо надстройка',
icon: 'Settings',
content: getSidebarContent()
})
};

// Определим содержимое вкладки
function getSidebarContent(): CustomSidebarContentPart[] {
// Создадим 3 элемента управления:

// текстовое поле для вывода текста
const infoText: Textblock = {
id: 'demo-plugin:sidebar:info',
type: 'textblock',
content: 'Демонстрационная надстройка'
};

// кнопка для вызова справки по надстройке
const aboutButton: Button = {
id: 'demo-plugin:sidebar:about-button',
type: 'button',
content: 'О надстройке',
// Просто используем повторно уже имеющийся колбэк
onClick: callbacks.showAbout,
};

// кнопка для выполнения действия
const doActionButton: Button = {
id: 'demo-plugin:sidebar:process-button',
type: 'button',
content: 'Обработать текст',
disabled: true, // Кнопка будет отключена по умолчанию
onClick: editorApi.createCallback(() =>
console.log('Пользователь нажал кнопку "Обработать текст"')
),
};

// Расположим элементы в стопку один под другим
return [{
type: 'controls',
orientation: 'vertical',
items: [infoText, aboutButton, doActionButton],
}];
}

После старта надстройки в боковой панели появится кнопка с иконкой "Settings" (одна из стандартных иконок редактора), при наведении на которую покажется подсказка с текстом "Демо надстройка".

Когда пользователь активирует эту панель, он увидит созданные надстройкой элементы управления и сможет с ними взаимодействовать.

Подробнее о компоновке элементов боковой панели описано в статье: Ориентация блоков внутри вкладки. Работа с иконками описана в API интерфейса HasIcon.icon, который является базовым для многих элементов управления.

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

Работа с событиями редактора

Давайте оживим элементы боковой панели, чтобы они зависели от состояния выделенного текста в редакторе.

Для организации подписок на события в редакторе создадим файл src / observe-editor-events.ts. Доступ к событиям в редакторе предоставляется через API editorApi.events.

Для реализации логики обновления элементов боковой панели расширим файл src / sidebar.ts

demo-plugin/
└── src/
└── observe-editor-events.ts
import { updateButtons } from './sidebar';

export function observeEditorEvents() {
// Подпишемся на событие смены выделения / позиции курсора в редакторе
editorApi.events.subscribe('selectionChange', (state: any) => {
// Обновим элементы управления боковой панели в зависимости от наличия выделенного текста
updateButtons(state.isSelection);
});

// Как пример, подпишемся на изменение в самом документе
editorApi.events.subscribe('documentChange', () => {
console.log('Что-то изменилось в документе');
});
}

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

Больше о событиях читайте в статье Работа с событиями документа.
Использование метода editorApi.ui.updateUiNodes подробно описано в его документации.

Подключение сторонних библиотек

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

важно

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

Заранее понять, какие сторонние библиотеки можно или нельзя подключать, определить сложно, но можно использовать чек-лист:

  1. Если библиотека содержит код, специфичный для исполнения в браузере (window, document, ...), то ее точно нельзя использовать в надстройке, т.к. в его окружении нет доступа к этим переменным.
  2. Если библиотека использует динамические импорты, то это не поддерживается в текущей реализации надстроек.
  3. Если библиотека предоставляет код, работающий в любом JavaScript-окружении, то она с большой вероятностью заработает и в надстройке.

Установка библиотеки

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

Установим библиотеку number-to-words-ru@2.4.1

npm install number-to-words-ru@2.4.1

Подключение библиотеки в надстройку

Используем код библиотеки точно так же, как и собственный код. Создадим под это новый файл number-to-words.ts:

demo-plugin/
└── src/
└── number-to-words.ts

И расширим имеющийся код надстройки для использования возможностей библиотеки.

import { convert } from 'number-to-words-ru';

type ConvertOptions = Parameters<typeof convert>[1];

const options: ConvertOptions = {
// настройки для библиотеки 'number-to-words-ru'
};

// Regex-паттерн для поиска чисел внутри строки
const numbersPattern = new Regex('...');

// Преобразует все найденные числа в строке в их строковое представление
export function transformNumbers(text: string): string {
return text.replaceAll(numbersPattern, (numberPart) => convert(numberPart, options));
};

Теперь надстройка по нажатию на кнопку в панели инструментов "Преобразовать числа":

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

А по нажатию на кнопку "Вставить текст" в боковой панели надстройка заменяет текущий выбранный текст в редакторе на обработанный текст, в котором все числа заменены на их строковое представление с помощью метода editorApi.document.insertContent().

В этом примере в файле src/actions.ts мы применили:

Скачать Демо надстройку

В демонстрационной надстройке сведены вместе все описанные в этой статье подходы и реализована функциональность для замены чисел в тексте на их строковое представление, для этого подключена библиотека numbers-to-words-ru.

Код надстройки структурирован в удобном для изучения виде и снабжен комментариями, поясняющими особенности работы с API редактора.

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

demo-plugin.zip

Вы можете скачать и ознакомиться к кодом демонстрационной надстройки, перейдя по ссылке: Скачать архив demo-plugin.zip