Corp/backend/static/js/components/chatBadgeHandler.js
Игорь Надеин aba011731f feat: implement Telegram-style badges system
- Created comprehensive badges-telegram-style.css (240 lines) based on Telegram Web K
- Updated all badge markup in templates (navbar, sidebar, chat_list, chat_detail, chat_title)
- Replaced Bootstrap badge classes with .badge-telegram classes
- Implemented Telegram specifications: 20px height, 10px radius, 11px font, 600 weight
- Added smooth animations: pulse (on update), animate-in (on mount), animate-out (on unmount)
- Updated JavaScript: replaced .d-none with style.display for badge visibility
- Updated 5 JS files: bottom-navbar, notification-manager, chatBadgeHandler, chatBadgeManager, chatListRealtime
- Added pulse animation on badge value increase
- Improved shadows and transitions with cubic-bezier easing
- Added badges-telegram-style.css to base.html for global availability
- Created documentation: TELEGRAM_BADGES_GUIDE.md, TELEGRAM_BADGES_UPDATE.md

Technical details:
- Research: Analyzed 50+ files from Telegram Web K GitHub repository
- Perfect circles: border-radius: 10px (not 50%)
- Spring animation: cubic-bezier(0.175, 0.885, 0.32, 1.275)
- Auto-hide: :empty and [data-count='0'] selectors
- Responsive design + dark theme support
2026-01-12 17:37:41 +03:00

131 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @module chatBadgeHandler
* @description Обработчик бейджа непрочитанных чатов в боковой панели.
* Автоматически обновляет счётчик непрочитанных сообщений, поддерживает real-time обновления.
*
* Пример HTML:
* <span id="sidebarChatBadge" data-count="0" class="badge d-none">0</span>
*
* Использование:
* import { initChatBadge } from './chatBadgeHandler.js';
* initChatBadge({ badgeId: 'sidebarChatBadge' });
*
* Real-time обновление:
* window.dispatchEvent(new CustomEvent('chats:unread-total', { detail: { total: 5 } }));
*/
/**
* Форматирует число для отображения в бейдже (99+).
* @param {number} count - Количество непрочитанных сообщений
* @returns {string} Отформатированная строка
*/
function formatCount(count) {
const value = Number(count) || 0;
return value > 99 ? '99+' : String(value);
}
/**
* Инициализирует обработчик бейджа непрочитанных чатов.
* @param {Object} options - Опции инициализации
* @param {string} [options.badgeId='sidebarChatBadge'] - ID элемента бейджа
* @param {string} [options.hiddenClass='d-none'] - Класс для скрытия бейджа (не используется в Telegram-стиле)
* @param {string} [options.eventName='chats:unread-total'] - Имя события для real-time обновлений
* @returns {Object} API с методами update, destroy
*/
export function initChatBadge(options = {}) {
const {
badgeId = 'sidebarChatBadge',
hiddenClass = 'd-none', // deprecated, используем style.display
eventName = 'chats:unread-total'
} = options;
const badge = document.getElementById(badgeId);
if (!badge) {
console.warn('initChatBadge: элемент бейджа не найден');
return { update: () => {}, destroy: () => {} };
}
/**
* Обновляет отображение бейджа (Telegram-стиль).
* @param {number} count - Новое количество непрочитанных сообщений
*/
function updateBadge(count) {
const value = Number(count) || 0;
const oldValue = Number(badge.dataset.count) || 0;
badge.dataset.count = String(value);
badge.textContent = formatCount(value);
// Telegram-стиль: используем style.display вместо классов
if (value <= 0) {
badge.style.display = 'none';
badge.classList.remove('pulse');
} else {
badge.style.display = '';
// Анимация pulse при увеличении
if (value > oldValue) {
badge.classList.add('pulse');
setTimeout(() => badge.classList.remove('pulse'), 600);
}
}
}
/**
* Обработчик real-time события обновления счётчика.
* @param {CustomEvent} event - Событие с данными { detail: { total: number } }
*/
function handleRealtimeUpdate(event) {
const total = event?.detail?.total;
if (typeof total !== 'undefined') {
updateBadge(total);
}
}
/**
* Fallback: подсчитывает общее количество непрочитанных из элементов на странице.
* Используется на страницах списка чатов, где каждый чат имеет [data-unread-count].
*/
function calculateTotalFromPage() {
const rows = document.querySelectorAll('[data-unread-count]');
if (!rows.length) return;
const total = Array.from(rows).reduce((sum, element) => {
return sum + (Number(element.textContent) || 0);
}, 0);
updateBadge(total);
}
// Начальная установка значения из data-count
updateBadge(badge.dataset.count);
// Подписка на real-time обновления
window.addEventListener(eventName, handleRealtimeUpdate);
// Fallback подсчёт при загрузке страницы
document.addEventListener('DOMContentLoaded', calculateTotalFromPage);
/**
* Функция для удаления обработчиков.
*/
function destroy() {
window.removeEventListener(eventName, handleRealtimeUpdate);
document.removeEventListener('DOMContentLoaded', calculateTotalFromPage);
}
return {
/**
* Публичный метод для обновления счётчика.
* @param {number} count - Новое значение
*/
update: updateBadge,
destroy
};
}
// Экспорт для совместимости с неModular кодом
if (typeof window !== 'undefined') {
window.initChatBadge = initChatBadge;
}