Runtime SDK: собственные блоки
Runtime SDK позволяет подключать блоки к установленному редактору. Исходники редактора и его пересборка для этого не нужны. Это руководство относится к модулю 0.133.0, интерфейсу 0.103.0 и SDK 1.
У расширения две части:
| Часть | Назначение | Пример |
|---|---|---|
editor |
Описывает узел, его редактирование и преобразование в HTML | Карточка с заголовком: поле ввода в редакторе и метод exportDOM(), создающий разметку карточки |
public |
Добавляет поведение к сохранённому HTML на странице сайта | Кнопка, раскрывающая подробности карточки |
Для статического блока public не нужен. Стили можно вынести в необязательный файл style. Для генерации HTML браузер и Node.js используют модуль editor.
Посетитель получает сохранённый HTML и разрешённые публичные ресурсы. Редактор и генератор HTML при посещении страницы не запускаются.
1. Соберите пример
Скачайте готовый проект расширения и распакуйте архив. Он содержит два примера: starter.notice описывает блок декларативно, а starter.native реализует собственный JavaScript-узел с формой параметров.
# Запустите команду из папки распакованного примера.
# Сборщик создаст новую папку package рядом с исходными файлами.
node build.mjs ./package
Выберите новое имя выходной папки, если package уже существует. Сборщик не перезаписывает готовый пакет. Node.js нужен на компьютере разработчика для запуска этого сборщика; условия его использования на сервере описаны ниже.
Результат сборки:
package/
├── browser/
│ ├── starter.native/1.4.0/ # editor, public и стили JavaScript-блока
│ └── starter.notice/1.1.0/ # JSON-описание декларативного блока
└── manifests/ # Манифесты для регистрации пакетов в PHP
Названия файлов, версии и контрольные суммы берите из созданных манифестов. Не правьте файлы после вычисления контрольных сумм.
2. Разместите файлы внутри проекта
Реестру нужны два значения: абсолютный путь к установленным файлам расширений и URL этой же папки. Выносить файлы за пределы сайта не требуется.
Доработка одного сайта
Скопируйте содержимое package/browser в /local/runtime-extensions/. Манифесты можно хранить рядом с PHP-кодом подключения, например в /local/php_interface/project-editor/manifests/.
// Корень сайта определяется установленным Битрикс.
$documentRoot = rtrim($_SERVER['DOCUMENT_ROOT'], '/');
// Первый аргумент — путь на диске, второй — соответствующий URL.
$registry = new ExtensionRegistry(
$documentRoot . '/local/runtime-extensions',
'/local/runtime-extensions/'
);
Расширение из Маркетплейса
Включите файлы из package/browser в установочные файлы своего модуля. Установщик может копировать их в /bitrix/js/project/editor/, сохраняя вложенные папки с идентификаторами и версиями пакетов. Манифесты и PHP-код регистрации остаются внутри вашего модуля.
// В этом варианте ресурсы устанавливает модуль project.editor.
$registry = new ExtensionRegistry(
rtrim($_SERVER['DOCUMENT_ROOT'], '/') . '/bitrix/js/project/editor',
'/bitrix/js/project/editor/'
);
Оба варианта используют один API. Они отличаются только местом установки и способом доставки файлов. Не помещайте пароли и закрытые данные в JavaScript, JSON-описания или публичные стили.
3. Зарегистрируйте пакет
Пример ниже подключает starter.native к заданному документу. Разместите его в PHP-коде своего модуля, который выполняется при подключении модуля к Битрикс. Рядом создайте папку manifests с манифестом из сборки.
<?php
use Bitrix\Main\Event;
use Bitrix\Main\EventManager;
use Bitrix\Main\Loader;
use GlobalIT\WYSIWYG\Extensions\ExtensionRegistry;
use GlobalIT\WYSIWYG\Extensions\ExtensionSelection;
/** Разрешает учебный блок одному редактору в одном документе. */
function registerProjectNotice(string $siteId, int $editorUserId, string $contentUid): void
{
if (!Loader::includeModule('globalit.wysiwyg')) {
throw new RuntimeException('Модуль globalit.wysiwyg не установлен');
}
// Манифест поставляется вместе с модулем расширения.
$manifestPath = __DIR__ . '/manifests/starter.native.json';
EventManager::getInstance()->addEventHandler(
'globalit.wysiwyg',
'GLOBALIT_WYSIWYG_CONFIGURE_RUNTIME_EXTENSIONS',
static function (Event $event) use ($siteId, $editorUserId, $contentUid, $manifestPath): void {
$selection = $event->getParameter(0);
// Настройки других документов этот обработчик не меняет.
if (!$selection instanceof ExtensionSelection
|| $selection->siteId !== $siteId
|| $selection->contentUid !== $contentUid
|| $selection->userId !== $editorUserId
) {
return;
}
$registry = new ExtensionRegistry(
rtrim($_SERVER['DOCUMENT_ROOT'], '/') . '/bitrix/js/project/editor',
'/bitrix/js/project/editor/'
);
$raw = file_get_contents($manifestPath);
if ($raw === false) {
throw new RuntimeException('Не удалось прочитать манифест расширения');
}
$registry->register(json_decode($raw, true, 64, JSON_THROW_ON_ERROR));
// Разрешение проверяется для каждого пакета, включая зависимости.
$selection->configure(
$registry,
['starter.native'],
static fn(string $id, string $version): bool =>
$id === 'starter.native' && $version === '1.4.0'
);
}
);
}
Вызовите функцию с идентификатором сайта, ID редактора и contentUid документа из своей интеграции. Для рабочего проекта замените учебные ограничения правилами доступа к своим материалам.
Открытие редактора, предпросмотр, импорт, регенерация и публичные скрипты имеют отдельные события настройки. Разрешение для одного сценария не разрешает остальные автоматически. Их назначения приведены в справочнике событий.
При регенерации событие GLOBALIT_WYSIWYG_CONFIGURE_REGENERATION_EXTENSIONS получает userId = 0, в том числе при запуске администратором из браузера. Здесь нужна политика фоновой обработки документа, а не проверка текущей пользовательской сессии.
Если расширений несколько
Зарегистрируйте их манифесты в одном реестре и передайте нужные идентификаторы в configure(). Вложенные папки с ID и версией предотвращают совпадение имён файлов. Метод ExtensionRegistry::collect($installationRoot, $publicRoot) дополнительно отправляет событие сбора манифестов; сам файлы не копирует.
4. Манифест пакета
Манифест связывает идентификатор и версию расширения с его файлами. Значение sha256 — SHA-256 содержимого файла, а не пути к нему.
| Поле | Значение и пример |
|---|---|
manifestVersion, sdkVersion |
Версия формата и SDK; обе равны 1 |
id |
Уникальное имя пакета, например project.notice |
version |
Точная версия из трёх чисел, например 1.0.0 |
nativeAbi |
Для JavaScript-узлов — значение ABI из совместимого starter |
dependencies |
Точные версии зависимостей, например {"project.icons":"1.0.0"}; без зависимостей — {} |
assets.editor |
Обязательный .mjs JavaScript-расширения |
assets.public |
Необязательный .mjs для посетителей |
assets.style |
Необязательный .css |
kind: "declarative", assets.descriptor |
Вместо JavaScript-узла используется JSON-описание; nativeAbi не задаётся |
Например, статическая карточка содержит editor и style. Для раскрывающейся карточки добавьте public. У декларативного предупреждения вместо editor будет descriptor: его структуру обрабатывает ядро.
В архиве примера сборщик создаёт оба манифеста с действительными контрольными суммами. Используйте их как рабочие примеры двух вариантов. Изменённый пакет выпускайте с новой версией; не заменяйте содержимое версии, на которую уже ссылаются документы.
Два готовых манифеста
Первый пакет реализует JavaScript-узел и публичное поведение. Второй описывает статический блок в JSON. Контрольные суммы ниже соответствуют файлам учебной сборки.
starter.native
{
"manifestVersion": 1,
"id": "starter.native",
"version": "1.4.0",
"kind": "native",
"nativeAbi": "glit-native-v1-lexical-0.50.0-react-19.2.8",
"sdkVersion": 1,
"dependencies": {},
"assets": {
"editor": {
"file": "native.mjs",
"sha256": "a11eb2d87d682e1df5f908df90392d3cabb0c6f341d5bc3b6ce341d2ae1f05f9"
},
"public": {
"file": "public.mjs",
"sha256": "2cee90e82c91842af71e48d6d580a76f14e3d17b783ab8d5d2cf19d22af43b86"
}
}
}
starter.notice
{
"manifestVersion": 1,
"id": "starter.notice",
"version": "1.1.0",
"kind": "declarative",
"sdkVersion": 1,
"dependencies": {},
"assets": {
"descriptor": {
"file": "notice.json",
"sha256": "db0bcee6f74bd05ccf5a7c0d4809d3b0ff4fcc2c99a84ee1a64ac3e0b89b6ca5"
},
"style": {
"file": "notice.css",
"sha256": "44475d467ebbe9e56b18f4ac08095d18ef256523eb617f0c807b8afbd80eecce"
}
}
}
5. Измените JavaScript-блок
Откройте src/native.mjs. Его синхронная функция activate(host) создаёт классы узлов и возвращает настройки расширения. Через host доступны React, Lexical, ABI, версия SDK и версии отдельных возможностей.
Поле результата activate() |
Что оно подключает |
|---|---|
nodes |
Классы узлов Lexical. Имя типа должно начинаться с идентификатора расширения |
palette |
Элементы панели вставки: id, label, nodeType, createNode, необязательный dialog |
properties |
Формы параметров: nodeType, title, React-компонент Component |
actions |
Команды панели инструментов: id, label, run, необязательные проверки enabled и active |
agentCommands |
Команды агента со схемами входных и выходных данных |
templateCodecs |
Правила копирования узлов при вставке шаблона |
attach(context) |
Обработчики событий и команды для экземпляра редактора в браузере |
dispose() |
Очистка ресурсов расширения |
Поля editorAttachmentVersion, paletteVersion, propertiesVersion, dialogVersion, agentCommandsVersion, templateCloneVersion и toolbarVersion сейчас равны 1. В activate() проверьте версии возможностей, которыми пользуется пакет; такая проверка уже есть в примере.
attach() вызывается синхронно. После подключения обработчика зарегистрируйте функцию его удаления через context.onDispose(cleanup). Так обработчик будет снят при закрытии редактора. Тем же способом освобождайте другие ресурсы.
Форма параметров
React-компонент формы получает четыре свойства:
| Свойство | Назначение |
|---|---|
nodeKey |
Ключ выбранного узла в текущем экземпляре Lexical |
json |
JSON выбранного узла, только для чтения |
readOnly |
Запрет редактирования |
commit(change) |
Синхронное изменение узла внутри операции редактора; возвращает boolean |
Проверьте введённые значения и вызовите, например, commit(node => node.setLabel(value)). Если метод вернул false, изменение не применено: пользователь мог выбрать другой узел, а документ, сессия или права — измениться. Обновите форму по текущим данным.
Меняйте узел только внутри commit. Объект json предназначен для чтения, а ссылку на узел нельзя использовать для последующих изменений вне commit. После изменения блока документ сохраняется обычным способом; commit не публикует страницу.
Диалог вставки получает readOnly, cancel() и submit(createNode). Передайте в submit синхронную функцию создания узла. Возврат false означает, что блок не вставлен.
Полные типы: native-runtime.ts в архиве TypeScript, реестр и серверные контексты — в архиве PHP.
Вывод на сервере и публичной странице
JavaScript-модуль расширения может работать и в редакторе, и при формировании HTML через Node.js на сервере. Отдельный PHP-рендерер для каждого блока не нужен. Не обращайтесь из activate() к объектам страницы браузера: эта функция вызывается и на сервере. DOM создавайте в методах узла, включая exportDOM().
PHP регистрирует пакет, проверяет права и подключает расширение к Битрикс. Для поведения блока у посетителей используйте assets.public: этот модуль добавляет интерактивность к готовому HTML.
Публичные скрипты требуют отдельного разрешения. Если оно не выдано, посетитель увидит статический HTML без загрузки скриптов расширения. Проверьте оба варианта по инструкции проверки интеграции.
Когда нужен Node.js
Node.js на сервере необязателен. Установленный редактор умеет создавать HTML в браузере. Node.js позволяет выполнять ту же работу на сервере, в том числе обрабатывать очередь без открытой вкладки.
| Операция | Как работает без Node.js |
|---|---|
| Редактирование, сохранение и публикация | Используется HTML, созданный редактором; сервер проверяет права, обрабатывает PHP-провайдеры и очищает разметку |
| Сохранение поля инфоблока | Используется HTML сохранённого снимка; привязка к полю и права проверяются повторно |
| Предпросмотр | Используется HTML выбранного черновика или ревизии |
| Импорт Runtime-документа | Сервер проверяет архив и переносит файлы. Браузер создаёт HTML из JSON с новыми путями к файлам |
| Восстановление целой ревизии | В черновик переносятся сохранённые JSON и HTML |
| Восстановление отдельного блока | Сервер объединяет JSON, браузер создаёт HTML, сервер проверяет версию черновика перед записью |
| Миграция Runtime-документа | PHP-обработчик преобразует JSON, браузер формирует HTML с новой версией расширения. Подтверждение миграции остаётся отдельным действием |
| Регенерация опубликованных страниц | Администратор запускает очередь в браузере |
| Показ страницы посетителю | Отдаётся сохранённый HTML; генерация при посещении не требуется |
Например, на обычном PHP-хостинге контент-менеджер может импортировать страницу с дополнительными карточками, восстановить один блок из истории и опубликовать результат. Устанавливать Node.js для этого не нужно. Если администратор изменил общий стиль кнопок, он может обновить уже опубликованные страницы через очередь в браузере.
На сервере с Node.js очередь может обрабатываться автоматически. Это полезно, когда страниц много или обновление должно выполняться по расписанию без участия администратора.
Что проверяет сервер
При восстановлении блока и миграции сервер отправляет браузеру подготовленный JSON и разрешённый набор расширений. Ответ привязан к пользователю, операции и исходным данным. Перед записью сервер повторно проверяет права, блокировку, версию документа и настройки. Если документ изменился, устаревший результат не сохраняется.
Подпись задания подтверждает его исходные данные, а не правильность HTML, возвращённого браузером. Этот HTML обрабатывается как обычный результат редактора: PHP-провайдеры формируют свои фрагменты на сервере, а очистка разметки удаляет недопустимый код. Проверки JavaScript-схем выполняются в браузере; это не замена серверной проверке прав.
Вызовы из собственных PHP-скриптов
Браузерное продолжение встроено в интерфейс редактора. Отдельный PHP-скрипт или cron-задание не может самостоятельно выполнить JavaScript расширения без Node.js. Если такой скрипт меняет JSON и должен сразу получить новый HTML, используйте Node.js либо выполняйте операцию через интерфейс редактора. Для очереди регенерации достаточно оставить задачи до запуска браузерной обработки.
Регенерация в браузере
Откройте настройки модуля и нажмите «Обработать в браузере», затем «Начать / продолжить». Действие доступно полному администратору Битрикс. Вкладка последовательно обрабатывает документы из очереди; запускать редактор каждого документа не нужно.
Исходный JSON, версия публикации и набор расширений берутся из базы. Браузер возвращает HTML. Перед сохранением сервер проверяет актуальность задачи и прав, выполняет PHP-провайдеры и очищает разметку. Изменившаяся за время обработки публикация не заменяется устаревшим результатом.
«Пауза» завершает текущую страницу и останавливает обработку. После закрытия вкладки незавершённая задача становится доступна повторно через пять минут. Повторно откройте страницу и нажмите «Начать / продолжить». До успешного завершения задачи посетители видят прежний HTML.
Если Node.js недоступен, фоновая обработка оставляет задачи в очереди для браузера. Успешная регенерация не меняет версии расширений и не выполняет миграцию JSON.