- 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
144 lines
4.5 KiB
JavaScript
144 lines
4.5 KiB
JavaScript
/**
|
||
* @fileoverview Chat Badge Manager - управление счётчиками непрочитанных сообщений
|
||
* Слушает события `chat:read` и обновляет бейджи для чатов
|
||
* @module components/chatBadgeManager
|
||
*/
|
||
|
||
/**
|
||
* Инициализирует менеджер бейджей непрочитанных сообщений
|
||
* @param {Object} options - Опции инициализации
|
||
* @param {string} [options.chatRowSelector='.chat-row'] - Селектор элементов чата
|
||
* @param {string} [options.badgeSelector='[data-unread]'] - Селектор бейджа
|
||
* @param {string} [options.countSelector='[data-unread-count]'] - Селектор счётчика
|
||
* @returns {Object|null} API менеджера или null
|
||
*/
|
||
export function initChatBadgeManager(options = {}) {
|
||
const {
|
||
chatRowSelector = '.chat-row',
|
||
badgeSelector = '[data-unread]',
|
||
countSelector = '[data-unread-count]'
|
||
} = options;
|
||
|
||
/**
|
||
* Обновляет бейдж для конкретного чата
|
||
* @param {string} chatId - ID чата
|
||
* @param {number} count - Количество непрочитанных (0 = скрыть бейдж)
|
||
*/
|
||
function updateBadge(chatId, count = 0) {
|
||
const row = document.querySelector(`${chatRowSelector}[data-chat-id="${chatId}"]`);
|
||
if (!row) return;
|
||
|
||
const badge = row.querySelector(badgeSelector);
|
||
const cntEl = row.querySelector(countSelector);
|
||
|
||
if (cntEl) {
|
||
cntEl.textContent = String(count);
|
||
}
|
||
|
||
if (badge) {
|
||
// Telegram-стиль: используем style.display и анимацию
|
||
const oldCount = parseInt(cntEl?.textContent || '0', 10);
|
||
if (count > 0) {
|
||
badge.style.display = '';
|
||
// Анимация при увеличении
|
||
if (count > oldCount) {
|
||
badge.classList.add('pulse');
|
||
setTimeout(() => badge.classList.remove('pulse'), 600);
|
||
}
|
||
} else {
|
||
badge.style.display = 'none';
|
||
badge.classList.remove('pulse');
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Получает текущее количество непрочитанных для чата
|
||
* @param {string} chatId - ID чата
|
||
* @returns {number} Количество непрочитанных
|
||
*/
|
||
function getBadgeCount(chatId) {
|
||
const row = document.querySelector(`${chatRowSelector}[data-chat-id="${chatId}"]`);
|
||
if (!row) return 0;
|
||
|
||
const cntEl = row.querySelector(countSelector);
|
||
if (!cntEl) return 0;
|
||
|
||
return Number(cntEl.textContent) || 0;
|
||
}
|
||
|
||
/**
|
||
* Увеличивает счётчик непрочитанных для чата
|
||
* @param {string} chatId - ID чата
|
||
* @param {number} delta - На сколько увеличить (по умолчанию 1)
|
||
*/
|
||
function incrementBadge(chatId, delta = 1) {
|
||
const current = getBadgeCount(chatId);
|
||
updateBadge(chatId, current + delta);
|
||
}
|
||
|
||
/**
|
||
* Сбрасывает бейдж (устанавливает 0)
|
||
* @param {string} chatId - ID чата
|
||
*/
|
||
function resetBadge(chatId) {
|
||
updateBadge(chatId, 0);
|
||
}
|
||
|
||
/**
|
||
* Обработчик события chat:read
|
||
* @param {CustomEvent} e - Событие с detail.chatId
|
||
*/
|
||
function handleChatRead(e) {
|
||
const chatId = e.detail?.chatId;
|
||
if (chatId) {
|
||
resetBadge(chatId);
|
||
}
|
||
}
|
||
|
||
// Подключаем обработчик события
|
||
window.addEventListener('chat:read', handleChatRead);
|
||
|
||
// API
|
||
return {
|
||
/**
|
||
* Программное обновление бейджа
|
||
* @param {string} chatId - ID чата
|
||
* @param {number} count - Количество непрочитанных
|
||
*/
|
||
updateBadge,
|
||
|
||
/**
|
||
* Получить количество непрочитанных
|
||
* @param {string} chatId - ID чата
|
||
* @returns {number}
|
||
*/
|
||
getBadgeCount,
|
||
|
||
/**
|
||
* Увеличить счётчик
|
||
* @param {string} chatId - ID чата
|
||
* @param {number} delta - На сколько увеличить
|
||
*/
|
||
incrementBadge,
|
||
|
||
/**
|
||
* Сбросить счётчик в 0
|
||
* @param {string} chatId - ID чата
|
||
*/
|
||
resetBadge,
|
||
|
||
/**
|
||
* Уничтожение обработчиков
|
||
*/
|
||
destroy: () => {
|
||
window.removeEventListener('chat:read', handleChatRead);
|
||
}
|
||
};
|
||
}
|
||
|
||
// Публикуем в window для совместимости
|
||
if (typeof window !== 'undefined') {
|
||
window.initChatBadgeManager = initChatBadgeManager;
|
||
}
|