Лента событий ui.timeline
- Подключить расширение
- Собрать ленту событий
- Передать параметры ленты
- Описать запись ленты
- Показать изменение стадии и полей
- Добавить свой тип записи
- Управлять записями ленты
- Показать запланированные задания
- Подгружать записи при прокрутке
- Добавить редактор над лентой
- Редактировать комментарии
- Подготовить комментарии на сервере
- Подписаться на события
- Связанные материалы
Лента событий показывает историю объекта: кто и когда изменил стадию, какие поля заполнил, какие комментарии оставил. Записи лента выводит в том порядке, в каком они переданы, и группирует по дням.
Ленту применяют на странице отдельного объекта — заявки, документа, задания, — когда пользователю нужна хронология изменений и обсуждение рядом с ней.
Расширение только отрисовывает переданные данные и сообщает о действиях пользователя событиями. Запросы к серверу оно не отправляет: обработчики событий пишет разработчик.
В Bitrix Framework за ленту событий отвечает расширение ui.timeline. Оно экспортирует объект Timeline с семью классами:
-
Stream— сама лента: хранит записи, отрисовывает разделы и бросает события. -
Item— базовый класс записи, от него наследуются все остальные типы. -
History— запись об изменении объекта, рисует блоки смены стадии и заполненных полей. -
StageChangeиFieldsChange— готовые типы записей для смены стадии и для заполненных полей. -
Editor— абстрактный редактор над лентой. -
CommentEditor— редактор комментариев, наследникEditor.
Подключить расширение
Если вы подключаете ленту из PHP, загрузите расширение ui.timeline.
\Bitrix\Main\UI\Extension::load('ui.timeline');
Вместе с расширением подключаются его зависимости: main.core, main.core.events, main.popup, main.date, main.loader, ui.dialogs.messagebox, ui.design-tokens и ui.fonts.opensans. Загружать их отдельно не нужно.
Если вы работаете в модульном JavaScript, импортируйте объект Timeline из ui.timeline.
import { Timeline } from 'ui.timeline';
Классы лежат внутри этого объекта: Timeline.Stream, Timeline.Item и остальные. Вне модульного JavaScript тот же объект доступен как BX.UI.Timeline. Общий порядок подключения расширений описан в статье Расширения.
Собрать ленту событий
Чтобы показать ленту на странице, выполните основные действия:
-
Создайте экземпляр
Stream. Передайте записи в параметреitems, а данные об авторах — в параметреusers. -
Вызовите метод
render(). Он собирает разметку ленты и возвращает корневой DOM-узел. -
Вставьте этот узел в контейнер на странице.
Пример. Лента из двух записей выводится в узел document-timeline: сначала создание документа, затем смена стадии.
import { Timeline } from 'ui.timeline';
const stream = new Timeline.Stream({
items: [
{
id: 1,
action: 'item_create',
title: 'Документ создан',
createdTimestamp: 1757923200000,
userId: 1,
},
{
id: 2,
action: 'stage_change',
title: 'Стадия изменена',
createdTimestamp: 1757930400000,
userId: 1,
data: {
stageFrom: { id: 1, name: 'Черновик' },
stageTo: { id: 2, name: 'На проверке' },
},
},
],
users: {
1: {
fullName: 'Оператор склада',
link: '/company/personal/user/1/',
photo: null,
},
},
});
document.getElementById('document-timeline').appendChild(stream.render());
Уже созданные узлы метод render() переиспользует и дорисовывает только те записи, которых еще нет в разметке. Поэтому повторный вызов не дублирует содержимое ленты.
Метод getContainer() возвращает корневой узел ленты. Пока render() не вызвали, узла нет и метод возвращает undefined.
Лента состоит из четырех разделов, которые идут сверху вниз: редакторы, прикрепленные записи, запланированные задания и общий список. Общий список лента делит на дни и ставит перед каждым днем разделитель с датой.
Передать параметры ленты
Все параметры конструктора необязательны. Если параметр не передан, лента использует значение по умолчанию.
type StreamOptions = {
items?: Array;
users?: Object;
nameFormat?: string;
pageSize?: number;
tasks?: Array;
editors?: Editor[];
itemClasses?: Array;
};
|
Параметр |
Тип |
Описание |
|
|
|
Задает записи общего списка. Для каждого набора данных лента создает запись и добавляет ее в список. По умолчанию пустой массив |
|
|
|
Задает данные об авторах записей. Ключ — идентификатор пользователя, значение — объект с полями |
|
|
|
Задает формат имени пользователя. Лента передает его каждой записи. По умолчанию значение не задано |
|
|
|
Задает число записей на страницу. Лента сравнивает с ним длину списка и решает, включать ли подгрузку при прокрутке. Нулевое и отрицательное значение лента заменяет на |
|
|
|
Задает записи раздела запланированных заданий. По умолчанию пустой массив |
|
|
|
Задает редакторы над лентой. Лента принимает только объекты класса |
|
|
|
Задает свои типы записей массивом пар «код типа — класс». По умолчанию список пуст |
Данные об авторах лента берет по идентификатору из поля userId записи. Каждое поле объекта пользователя она показывает в своем месте:
-
fullName— всплывающая подсказка аватара, -
link— адрес ссылки на страницу пользователя, -
photo— фон аватара.
Если автора нет в списке users, лента рисует пустую ссылку вместо аватара.
Описать запись ленты
Запись ленты — это объект данных, а не готовый DOM. Лента выбирает по этим данным класс отрисовки, создает запись и хранит ее у себя.
|
Поле |
Тип |
Описание |
|
|
|
Задает идентификатор записи. По нему лента находит запись методами |
|
|
|
Задает код типа записи. По коду лента выбирает класс отрисовки. По умолчанию пустая строка |
|
|
|
Задает имя класса отрисовки строкой, например |
|
|
|
Задает заголовок записи. Лента показывает его в верхней строке вместе со временем и аватаром автора и экранирует при выводе. По умолчанию пустая строка |
|
|
|
Задает содержимое записи. Лента вставляет значение в разметку как HTML. По умолчанию пустая строка |
|
|
|
Задает содержимое записи комментария в HTML. Классы записей об изменениях это поле не выводят. По умолчанию пустая строка |
|
|
|
Задает содержимое записи без разметки. По умолчанию пустая строка |
|
|
|
Задает время создания записи в миллисекундах. Лента показывает его в заголовке записи и группирует по нему записи по дням. По умолчанию значение не задано |
|
|
|
Задает идентификатор автора записи. По нему лента берет данные из параметра |
|
|
|
Помечает запись прикрепленной. Лента дополнительно создает копию записи в разделе прикрепленных. По умолчанию |
|
|
|
Задает данные о связанных объектах: стадиях, полях, задании и способе изменения. По умолчанию пустой объект |
Если передать время в секундах, все записи попадут в 1970 год и разделители дней соберутся в один день.
Без положительного значения в этом поле лента не определит день записи. Метод insertItem() в таком случае добавит запись в список, но не покажет ее в разметке.
Поля description и htmlDescription лента вставляет в разметку как HTML, без экранирования. Заголовок записи, названия стадий и названия полей она экранирует сама. Если в содержимое записи попадает текст, который ввел пользователь, экранируйте или очищайте его на сервере.
Код в поле action лента сопоставляет с классом отрисовки по своей карте типов.
|
Код типа |
Класс |
Что показывает запись |
|
|
|
Заголовок, время и автора. Подходит для записи о создании объекта |
|
|
|
Смену стадии и список заполненных полей |
|
|
|
Только список заполненных полей |
|
|
|
Комментарий с кнопкой раскрытия, вложениями и меню правки |
Если кода в action нет в карте типов, лента использует класс History.
Поле events в данных записи лента заполняет сама: она подставляет туда свои обработчики нажатия на скрепку, удаления и ошибки. Передавать его не нужно.
Показать изменение стадии и полей
Классы StageChange и FieldsChange берут подробности из объекта data. Заголовки блоков лента подставляет сама: «Изменена стадия» и «Поля заполнены».
|
Поле |
Тип |
Описание |
|
|
|
Задает стадию до изменения объектом с полями |
|
|
|
Задает стадию после изменения объектом с полями |
|
|
|
Задает список заполненных полей. Лента показывает значение |
|
|
|
Задает данные объекта до изменения. Метод |
|
|
|
Задает задание, при выполнении которого появилась запись, объектом с полями |
|
|
|
Задает способ изменения. От него зависит значок записи |
Блок смены стадии лента рисует, только если заданы обе стадии и их идентификаторы различаются. Если стадия не менялась, запись покажет лишь заполненные поля.
Поле scope принимает одно из четырех значений.
|
Значение |
Способ изменения |
|
|
Пользователь изменил объект вручную. Лента считает способ ручным и когда поле |
|
|
Объект изменил робот. Такой записи лента ставит значок автоматического действия |
|
|
Изменение сделано при выполнении задания |
|
|
Изменение пришло из приложения через REST |
Пример. Запись показывает переход между стадиями и два заполненных поля, а значок отмечает, что объект изменил робот.
{
id: 3,
action: 'stage_change',
title: 'Стадия изменена',
createdTimestamp: 1757934000000,
userId: 1,
data: {
scope: 'automation',
stageFrom: { id: 2, name: 'На проверке' },
stageTo: { id: 3, name: 'Утвержден' },
fields: [
{ title: 'Срок хранения' },
{ title: 'Приоритет' },
],
},
}
Добавить свой тип записи
Готовых типов записей четыре: item_create, stage_change, fields_change и comment. Если ни один из них не подходит, напишите свой класс записи и свяжите его с собственным кодом типа.
Написать класс записи
Унаследуйте класс от Timeline.History и переопределите метод renderMain(). Он возвращает DOM-узел с содержимым записи, а заголовок, время и аватар автора лента рисует сама.
Пример. Класс DocumentSigned показывает в записи название документа из данных data.item.
import { Tag, Text } from 'main.core';
import { Timeline } from 'ui.timeline';
class DocumentSigned extends Timeline.History
{
renderMain()
{
const item = this.getItem();
return Tag.render`
<div class="ui-item-detail-stream-content-detail">
${Text.encode(item ? item.name : '')}
</div>
`;
}
}
Класс ui-item-detail-stream-content-detail в примере задает шрифт содержимого записи: размер 13 пикселей и межстрочный интервал 19 пикселей. Без него содержимое возьмет шрифт страницы.
Внутри своего класса доступны методы базовой записи.
|
Метод |
Описание |
|
|
Возвращает способ изменения из поля |
|
|
Возвращает |
|
|
Возвращает |
|
|
Возвращает |
|
|
Возвращает |
|
|
Возвращает дату создания записи. Возвращает |
|
|
Форматирует переданную дату в строку времени без даты |
|
|
Возвращает поля записи, которые принимает метод |
|
|
Перерисовывает содержимое записи по текущим данным |
Класс History дает еще три точки для переопределения. Метод renderHeader() рисует верхнюю строку с заголовком, временем и аватаром автора. Методы renderStageChange() и renderFieldsChange() возвращают готовые блоки смены стадии и заполненных полей — их можно вставить в свою разметку вместо того, чтобы собирать заново.
Своей записи можно дать меню действий. Лента строит его на меню из расширения main.popup. Переопределите метод getActions() — он возвращает массив пунктов, у каждого пункта есть текст в поле text и обработчик нажатия в поле onclick. Базовый класс возвращает пустой массив, поэтому у записи без своих действий кнопки меню нет.
Пример. Метод добавляет записи один пункт меню.
getActions()
{
return [
{
text: 'Открыть документ',
onclick: this.openDocument.bind(this),
},
];
}
Меню после нажатия на пункт остается открытым. Закройте его в обработчике вызовом this.getActionsMenu().close().
Связать класс с кодом типа
Готовый класс лента сама не подхватит: нужно сказать ей, какому коду типа он соответствует. Сделать это можно тремя способами, и выбор зависит от того, кто создает ленту и откуда приходит тип записи.
Первый способ — параметр itemClasses конструктора. Передайте в нем пары «код типа — класс».
Пример. Записи с кодом document_signed лента отрисует классом DocumentSigned.
const stream = new Timeline.Stream({
items: documentHistory,
itemClasses: [
['document_signed', DocumentSigned],
],
});
Коды item_create, stage_change, fields_change и comment лента записывает в карту типов после ваших значений, поэтому переопределить их через itemClasses нельзя. Для своей записи выбирайте другой код типа.
Второй способ — событие onAfterInit. Он нужен, когда ленту создает чужой компонент и параметры конструктора вам недоступны. Лента бросает это событие в конце работы конструктора и передает себя в его данных.
Пример. Обработчик добавляет свой тип записи в ленту, созданную чужим кодом.
import { EventEmitter } from 'main.core.events';
EventEmitter.subscribe('BX.UI.Timeline.Stream:onAfterInit', (event) => {
const stream = event.getData().stream;
stream.itemClasses.set('document_signed', DocumentSigned);
});
Подпишитесь на событие до того, как чужой код создаст ленту. Обработчик, добавленный позже, не сработает.
Третий способ — поле itemClassName в данных самой записи. Оно принимает имя класса строкой, и лента находит класс по этому имени в глобальной области видимости. Способ подходит, когда тип записи приходит с сервера вместе с данными.
Управлять записями ленты
Лента хранит записи в трех списках: общий список, прикрепленные записи и запланированные задания. У каждого списка свои методы чтения, а методы изменения принимают уже созданный объект записи.
|
Метод |
Описание |
|
|
Создает запись по данным и возвращает ее. Второй параметр принимает конструктор класса и отменяет выбор класса по коду типа. Возвращает |
|
|
Добавляет запись в конец общего списка и возвращает ленту. Разметку метод не меняет, поэтому после него вызывают |
|
|
Дорисовывает записи общего списка, которых еще нет в разметке |
|
|
Вставляет запись в начало ленты с анимацией и добавляет разделитель дня, если для этого дня его еще нет. Подходит для записи, которую пользователь только что создал. Запись с уже занятым идентификатором метод пропускает |
|
|
Удаляет запись с анимацией из того списка, где она находится |
|
|
Добавляет копию записи в раздел прикрепленных с анимацией |
|
|
Убирает запись из раздела прикрепленных с анимацией |
|
|
Возвращает массив записей общего списка |
|
|
Возвращает запись общего списка по идентификатору |
|
|
Возвращает массив прикрепленных записей |
|
|
Возвращает прикрепленную запись по идентификатору |
Прикрепленная запись — это отдельная копия записи из общего списка с тем же идентификатором. Методы getItem() и getPinnedItem() вернут для одного идентификатора два разных объекта, и обновлять после правки нужно оба.
Пример. Код обновляет обе копии записи после успешного сохранения на сервере.
const item = stream.getItem(commentId);
if (item)
{
item.update(commentData);
}
const pinnedItem = stream.getPinnedItem(commentId);
if (pinnedItem)
{
pinnedItem.update(commentData);
}
Метод update(data) записи принимает только четыре поля:
-
description— текст записи, -
htmlDescription— содержимое записи в HTML, -
data— данные о стадиях, полях и способе изменения, -
userId— идентификатор автора записи.
Метод заменяет те поля, что переданы, и перерисовывает содержимое записи. Заголовок и время записи он не меняет.
При нажатии на скрепку лента переключает раздел прикрепленных сама: меняет признак isFixed записи, добавляет или убирает копию и только после этого бросает событие onPinClick. Обработчику остается сохранить новое состояние на сервере — оно лежит в поле isFixed записи из данных события.
Пример. Обработчик сохраняет новое состояние скрепки.
stream.subscribe('onPinClick', (event) => {
const item = event.getData().item;
BX.ajax.runAction('example.Timeline.updateIsFixed', {
data: {
id: item.getId(),
isFixed: item.isFixed ? 'y' : 'n',
},
});
});
Методы pinItem() и unPinItem() нужны, когда состав прикрепленных записей меняет не пользователь: например, при восстановлении состояния после загрузки данных с сервера. Вызывать их в обработчике onPinClick не нужно — лента уже применила изменение.
Показать запланированные задания
Раздел запланированных заданий лента показывает над общим списком под заголовком «Запланировано». Записи для него передают в параметре tasks конструктора.
Лента хранит эти записи отдельно от общего списка: метод getItems() их не возвращает, копии в разделе прикрепленных для них не создаются. Для чтения используйте методы getTasks() и getTask(id).
Метод updateTasks(tasks) приводит раздел к новому набору данных. Лента сравнивает наборы по идентификаторам записей и действует по-разному:
-
записи, которой нет в новом наборе, — удаляет с анимацией,
-
новую запись — добавляет с анимацией,
-
совпавшую по идентификатору — обновляет через метод
update().
Когда новый набор пуст, лента убирает и заголовок раздела. Вызывайте метод после того, как сервер подтвердил, что состав заданий изменился.
Пример. Раздел запланированных заданий обновляется после ответа сервера.
stream.updateTasks([
{
id: 10,
action: 'item_create',
title: 'Проверить комплектность',
createdTimestamp: 1757937600000,
userId: 1,
},
]);
Подгружать записи при прокрутке
Страницы лента не хранит и не запрашивает. Событием onScrollToTheBottom она только сообщает, что пользователь долистал список до конца.
Обработку прокрутки лента включает сама, если при готовности документа записей в общем списке не меньше значения pageSize. Меньшее число записей лента считает полной историей и обработку не включает. Методы enableLoadOnScroll() и disableLoadOnScroll() включают и выключают ее вручную.
Пока лента в состоянии загрузки, событие не повторяется. Поэтому в обработчике сначала вызывайте startProgress(), а после ответа сервера — stopProgress().
Пример. Обработчик догружает очередную страницу, добавляет записи в конец списка и отключает подгрузку, когда сервер вернул пустую страницу.
stream.subscribe('onScrollToTheBottom', () => {
stream.startProgress();
loadNextPage().then((items) => {
stream.stopProgress();
if (items.length <= 0)
{
stream.disableLoadOnScroll();
return;
}
items.forEach((data) => {
const item = stream.createItem(data);
if (item)
{
stream.addItem(item);
}
});
stream.renderItems();
});
});
Метод startProgress() показывает индикатор загрузки у последней записи списка, stopProgress() убирает его, isProgress() возвращает текущее состояние. Та же пара методов есть у записи — там индикатор появляется внутри ее контейнера.
Добавить редактор над лентой
Редактор — это блок над лентой, через который пользователь создает новые записи. Редакторы передают в параметре editors конструктора, и лента показывает их в самом верху.
Класс Editor абстрактный. Его метод render() бросает ошибку This method should be overridden, поэтому создавать экземпляр Editor напрямую нельзя — наследуйте от него свой класс и переопределите отрисовку.
|
Метод |
Описание |
|
|
Возвращает идентификатор редактора. Лента использует его ключом в своем списке редакторов |
|
|
Возвращает заголовок редактора. В базовом классе не реализован |
|
|
Отрисовывает редактор и возвращает DOM-узел. Метод нужно переопределить |
|
|
Возвращает контейнер отрисованного редактора |
|
|
Возвращает |
|
|
Очищает разметку редактора. Со значением |
|
|
Показывает индикатор загрузки внутри редактора |
|
|
Убирает индикатор загрузки |
Параметр id конструктора задает идентификатор редактора. Если передать пустую строку или значение другого типа, редактор подставит случайный идентификатор, и обратиться к нему по имени вы не сможете.
Об ошибках редактор сообщает событием error с текстом в поле message. Лента подписывается на это событие у каждого переданного редактора и выводит текст в консоль браузера. Свое сообщение пользователю покажите в собственном обработчике.
Редактировать комментарии
Класс CommentEditor — готовый редактор комментариев и единственный наследник Editor в расширении. Он рисует поле ввода, подгружает визуальный редактор и бросает события на сохранении. Запросы к серверу он не отправляет, поэтому работа с комментариями складывается из трех частей: подключить редактор, отдать ему разметку визуального редактора и сохранить то, что ввел пользователь.
Подключить редактор комментариев
Конструктор принимает два параметра.
|
Параметр |
Тип |
Описание |
|
|
|
Задает идентификатор редактора. Пустое значение редактор заменяет случайным |
|
|
|
Задает идентификатор комментария, если редактор правит существующую запись. Значение принимается только числом: строку редактор пропустит. По умолчанию |
Пример. Редактор комментариев добавлен в ленту первым блоком.
import { Timeline } from 'ui.timeline';
const commentEditor = new Timeline.CommentEditor({
id: 'DocumentCommentEditor',
});
const stream = new Timeline.Stream({
items: documentHistory,
editors: [
commentEditor,
],
});
Загрузить визуальный редактор
Визуальный редактор расширение не содержит: его HTML приходит с сервера. Когда пользователь ставит курсор в поле ввода, редактор бросает асинхронное событие onLoadVisualEditor и показывает индикатор загрузки. Обработчик должен вернуть промис и положить готовую разметку в поле html данных события.
Пример. Обработчик запрашивает разметку визуального редактора и передает ее в данные события.
import { EventEmitter } from 'main.core.events';
EventEmitter.subscribe('BX.UI.Timeline.CommentEditor:onLoadVisualEditor', (event) => {
return new Promise((resolve, reject) => {
BX.ajax.runAction('example.Comment.getVisualEditor', {
data: {
name: event.getData().name,
commentId: event.getData().commentId,
},
}).then((response) => {
event.getData().html = response.data.html;
resolve();
}).catch(reject);
});
});
Сохранить комментарий
Сохранение устроено так же, как загрузка редактора: расширение бросает асинхронное событие и ждет промис. По кнопке отправки редактор проверяет, что текст не пуст, и бросает событие onSave с текстом в поле description, идентификаторами вложений в поле files и идентификатором комментария в поле commentId. Нулевой commentId означает новый комментарий, положительный — правку существующего.
Обработчик сохраняет комментарий и кладет данные новой записи в поле comment данных события. После разрешения промиса редактор очищает поле ввода, бросает событие afterSave и сворачивается. Если промис отклонен, редактор читает поле message данных события и бросает событие error с этим текстом.
Новую запись в ленту редактор не добавляет: создайте ее в обработчике сами.
Пример. Обработчик сохраняет комментарий и вставляет новую запись в начало ленты.
EventEmitter.subscribe('BX.UI.Timeline.CommentEditor:onSave', (event) => {
return new Promise((resolve, reject) => {
BX.ajax.runAction('example.Comment.add', {
data: {
description: event.getData().description,
files: event.getData().files,
},
}).then((response) => {
event.getData().comment = response.data.comment;
const item = stream.createItem(response.data.comment);
if (item)
{
stream.insertItem(item);
}
resolve();
}).catch((response) => {
event.getData().message = response.errors[0].message;
reject();
});
});
});
Событие beforeSave редактор бросает до начала сохранения и передает в нем текст и вложения. Используйте его, чтобы записать черновик или снять метрику.
Показать комментарий в ленте
Готовую запись комментария лента отрисовывает классом Comment. Из объекта Timeline этот класс не экспортируется: лента создает его сама для записей с кодом типа comment.
Комментарий приходит в ленту не полностью. Длинный текст и вложения запись догружает отдельными асинхронными событиями по действию пользователя.
|
Событие |
Когда срабатывает |
Что вернуть обработчику |
|
|
При нажатии кнопки развертывания длинного комментария |
Объект в поле |
|
|
При отрисовке блока вложений |
Готовый HTML блока вложений в поле |
|
|
После подтверждения удаления в диалоге |
Ничего. Достаточно разрешить промис: запись удалится из ленты сама |
Кнопку развертывания запись показывает, если текст в поле textDescription длиннее 128 символов или к комментарию приложены файлы. Меню записи содержит пункты «Изменить» и «Удалить»: первый переключает запись в режим правки, второй показывает диалог подтверждения из расширения ui.dialogs.messagebox и бросает событие onDelete.
Те же действия доступны из кода методами записи комментария.
|
Метод |
Описание |
|
|
Переключает запись в режим правки и показывает редактор |
|
|
Возвращает запись в режим просмотра |
|
|
Запускает догрузку полного текста комментария через событие |
|
|
Запускает догрузку блока вложений через событие |
|
|
Возвращает редактор комментария, создавая его при первом вызове. Имя метода написано с опечаткой в исходном коде расширения |
События onLoadContent, onLoadFilesContent и onDelete обрабатывают отклоненный промис одинаково: запись берет текст из поля message данных события и бросает событие error.
Подготовить комментарии на сервере
Серверную часть комментариев обслуживают два класса пространства имен Bitrix\UI\Timeline. Через них отвечают на асинхронные события редактора: собирают HTML визуального редактора, разбирают текст и рассылают уведомления об упоминаниях.
Класс Bitrix\UI\Timeline\Comment строит ответы для интерфейса. Конструктор принимает идентификатор объекта пользовательских полей и имя поля для вложений — через них класс находит файлы, приложенные к комментарию.
-
getVisualEditorResponse($name, $id, $text)— собирает компонентbitrix:main.post.formс настроенным набором кнопок и возвращает его. Результат возвращают из действия контроллера, которое вызывает обработчик событияonLoadVisualEditor. -
getFilesContentResponse($id)— собирает компонент показа вложений и возвращает его. Возвращаетnull, если у объекта нет приложенных файлов. -
saveFiles($id, $files)— сохраняет вложения в пользовательское поле объекта и возвращает признак успеха. -
getFileUserFields($id)— возвращает пользовательские поля объекта с файлами. Без установленного модуляdiskвозвращает пустой массив. -
sendMentions($id, $fromUserId, $text, $message, $previouslyMentionedUserIds, $options)— отправляет уведомления пользователям, упомянутым в тексте, и возвращает идентификаторы отправленных сообщений. Автора комментария и ранее упомянутых пользователей метод пропускает, а два последних параметра необязательны.
Класс CommentParser разбирает текст комментария, который пришел из визуального редактора в формате BB-кодов.
-
getHtml($text)— преобразует BB-коды в HTML и раскодирует эмодзи. Результат подходит для поляhtmlDescriptionзаписи. -
getText($text)— убирает из текста всю разметку. Результат подходит для поляtextDescriptionзаписи и для поиска. -
getMentionedUserIds($text)— возвращает идентификаторы пользователей, упомянутых в тексте. -
setUserFields($userFields)— задает пользовательские поля для разбора текста и возвращает сам объект.
Пример. Код разбирает текст комментария и готовит данные записи для ленты.
use Bitrix\UI\Timeline\CommentParser;
$parser = new CommentParser();
$comment = [
'id' => $commentId,
'action' => 'comment',
'htmlDescription' => $parser->getHtml($text),
'textDescription' => $parser->getText($text),
'createdTimestamp' => $createdTime->getTimestamp() * 1000,
'userId' => $userId,
];
Разбор текста опирается на модули blog, forum и socialnetwork: класс берет первый установленный из них. Если ни один не установлен, он использует базовый разборщик текста, и часть BB-кодов останется необработанной. Уведомления об упоминаниях уходят только с установленным модулем im, а вложения работают только с модулем disk.
Подписаться на события
Лента и ее части бросают события через EventEmitter из расширения main.core.events. У событий ленты, редакторов и комментариев есть два способа подписки: метод subscribe() на объекте и глобальная подписка по полному имени события.
Пространства имен различаются по классам.
|
Класс |
Пространство имен |
|
|
|
|
|
|
|
|
|
|
|
|
Пространство имен записи задано без префикса BX. Для глобальной подписки на событие записи используйте имя UI.Timeline.Item:onPinClick, а не BX.UI.Timeline.Item:onPinClick.
Лента и запись бросают свои события синхронно.
|
Событие |
Когда срабатывает |
Данные события |
|
|
В конце работы конструктора ленты |
|
|
|
После отрисовки ленты |
Нет |
|
|
При прокрутке до конца общего списка |
Нет |
|
|
При нажатии на скрепку записи |
|
|
|
При нажатии на скрепку конкретной записи |
Нет |
|
|
После удаления записи из ленты |
Нет |
|
|
При ошибке в записи |
|
События редактора и комментария делятся на синхронные и асинхронные. Асинхронное событие ждет промис от обработчика и продолжает работу после его разрешения. Через такие события расширение получает данные с сервера.
|
Событие |
Тип |
Когда срабатывает |
|
|
Асинхронное |
При первом переходе в поле ввода комментария |
|
|
Синхронное |
Перед сохранением комментария |
|
|
Асинхронное |
При сохранении комментария |
|
|
Синхронное |
После того, как обработчик |
|
|
Синхронное |
При отмене правки и после успешного сохранения |
|
|
Синхронное |
При ошибке загрузки редактора или сохранения |
|
|
Асинхронное |
При развертывании длинного комментария |
|
|
Асинхронное |
При отрисовке блока вложений |
|
|
Асинхронное |
После подтверждения удаления комментария |
|
|
Синхронное |
При ошибке загрузки содержимого или удаления |
На события конкретного объекта подписывайтесь методом subscribe(): так обработчик получит события только этой ленты.
stream.subscribe('onAfterRender', () => {
stream.getContainer().classList.add('document-timeline—ready');
});
Глобальная подписка нужна для классов, экземпляры которых создает сама лента. Объекта комментария у вас нет, поэтому его события перехватывают по полному имени.
import { EventEmitter } from 'main.core.events';
EventEmitter.subscribe('BX.UI.Timeline.Comment:onDelete', (event) => {
return deleteComment(event.getData().commentId);
});
Связанные материалы
-
Всплывающие окна и меню main.popup — меню записи и всплывающее сообщение о пустом комментарии.
-
Окно сообщения — диалог подтверждения при удалении комментария.
-
Загрузка файлов Uploader — прием файлов от браузера, если вы собираете свой редактор с вложениями.
-
Работа с эмодзи — кодирование и декодирование эмодзи в тексте комментария.
-
Расширения — подключение JavaScript-расширений Bitrix Framework.