- 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
131 lines
4.8 KiB
JavaScript
131 lines
4.8 KiB
JavaScript
/**
|
||
* @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;
|
||
}
|