Corp/backend/static/js/components/chatBadgeManager.js
Игорь Надеин 0375337543 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

144 lines
4.5 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.

/**
* @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;
}