feat: отметки «@» в чате заявки и письма через поток Power Automate

В поле ввода чата «@» открывает подсказку с участниками заявки (инициатор
и все из маршрута). Отметка — обычный текст «@ФИО», без штатного упоминания
SharePoint, чтобы не уходило его собственное письмо.

После отправки сообщения портал вызывает поток FLOW_CHAT_NOTIFY_URL:
отмеченным — «Вас упомянули», инициатору — «Новое сообщение по вашей
заявке». О своих сообщениях писем нет; для проверки это включается
свойством веб-части «Чат: уведомлять о своих сообщениях» (по умолчанию
выключено) — тогда же можно отметить себя.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
gorbenko.pavel
2026-10-14 14:06:07 +05:00
parent d4b8c4e7e4
commit e38156fd1c
16 changed files with 658 additions and 32 deletions
@@ -0,0 +1,70 @@
import {
buildParticipants,
extractMentionedEmails,
filterParticipants,
findMentionQuery,
insertMention
} from './chatMentions';
const ivanov = { name: 'Иван Иванов', email: 'ivanov@tnsu.kz' };
const petrova = { name: 'Анна Петрова', email: 'petrova@tnsu.kz' };
describe('buildParticipants', () => {
it('убирает повторы, пустые адреса и самого пишущего', () => {
const result = buildParticipants(
[ivanov, { name: 'Иван Иванов', email: 'IVANOV@tnsu.kz' }, { name: 'Без почты', email: '' }, undefined, petrova],
'petrova@tnsu.kz'
);
expect(result).toEqual([ivanov]);
});
it('без ФИО показывает почту', () => {
expect(buildParticipants([{ email: 'x@tnsu.kz' }], '')).toEqual([{ name: 'x@tnsu.kz', email: 'x@tnsu.kz' }]);
});
});
describe('findMentionQuery', () => {
it('находит отметку в начале и после пробела', () => {
expect(findMentionQuery('@Ив', 3)).toEqual({ start: 0, query: 'Ив' });
expect(findMentionQuery('Привет @Иван Ив', 15)).toEqual({ start: 7, query: 'Иван Ив' });
});
it('не считает отметкой адрес почты и перенос строки', () => {
expect(findMentionQuery('mail@tnsu', 9)).toBeUndefined();
expect(findMentionQuery('@Иван\nдалее', 11)).toBeUndefined();
expect(findMentionQuery('без отметки', 5)).toBeUndefined();
});
});
describe('filterParticipants', () => {
it('ищет по началу слов ФИО и по почте', () => {
expect(filterParticipants([ivanov, petrova], 'пет')).toEqual([petrova]);
expect(filterParticipants([ivanov, petrova], 'иван ив')).toEqual([ivanov]);
expect(filterParticipants([ivanov, petrova], 'petrova@')).toEqual([petrova]);
expect(filterParticipants([ivanov, petrova], '')).toEqual([ivanov, petrova]);
});
});
describe('insertMention', () => {
it('заменяет набранное на ФИО и ставит курсор за пробелом', () => {
const text = 'Привет @Ив, глянь';
const result = insertMention(text, { start: 7, query: 'Ив' }, 10, ivanov);
expect(result.text).toBe('Привет @Иван Иванов , глянь');
expect(result.caret).toBe('Привет @Иван Иванов '.length);
});
});
describe('extractMentionedEmails', () => {
it('берёт отметки из текста без повторов', () => {
expect(extractMentionedEmails('@Иван Иванов и @Анна Петрова, @Иван Иванов', [ivanov, petrova])).toEqual([
'ivanov@tnsu.kz',
'petrova@tnsu.kz'
]);
});
it('стёртая отметка не считается, короткое ФИО не цепляется за длинное', () => {
const ivan = { name: 'Иван', email: 'ivan@tnsu.kz' };
expect(extractMentionedEmails('@Иван Иванов, привет', [ivan, ivanov])).toEqual(['ivanov@tnsu.kz']);
expect(extractMentionedEmails('Иван Иванов, привет', [ivanov])).toEqual([]);
});
});