Corp/backend/static/js/managers/unreadBadgeManager.js
Игорь Надеин 4a4f3b2fa0 refactor: Clean architecture + unified badge styling
PART 1: Extract UnreadBadgeManager (Clean Architecture)

Created managers/unreadBadgeManager.js:
✅ Single Responsibility: Manages ONLY badge UI
✅ Methods: increment(), reset(), setCount(), hasUnread()
✅ Handles all DOM manipulation internally
✅ Reusable and testable

Cleaned ChatControllerV2:
❌ Removed 150+ lines of UI logic
❌ Removed duplicate systems (_unreadCount + _newMessagesCount)
❌ Removed deprecated methods:
   - _incrementUnreadBadge()
   - _resetUnreadBadge()
   - _showNewMessagesIndicator()
   - _hideNewMessagesIndicator()
   - _findOrCreateNewMessagesButton()
✅ Now uses: this.badgeManager.increment/reset()

PART 2: Unified Badge Styling

Aligned unread-badge with sticky-date design:
- Same typography (font-size, weight, line-height)
- Same spacing (padding: 6px 12px)
- Same border-radius (10-12px)
- Same shadows (box-shadow hierarchy)
- Same transitions (0.2s ease)
- Same animation patterns (scale, hover effects)
- Added border: 2px for contrast with background

Result:
✅ Consistent UI across all badges
✅ Matches Telegram-like design system
✅ Better visual hierarchy
✅ ~950 lines in Controller (was 1125)
✅ Clean separation of concerns
2026-01-13 15:36:17 +03:00

150 lines
4.1 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 UnreadBadgeManager - управление badge непрочитанных на scrollBtn
* @module managers/unreadBadgeManager
*
* ОТВЕТСТВЕННОСТЬ:
* - Создание и управление badge элементом
* - Увеличение/сброс счетчика
* - Визуальные эффекты (анимации, классы)
* - НЕТ бизнес-логики (когда показывать/скрывать)
*/
/**
* Управляет badge непрочитанных сообщений на кнопке скролла
*/
export class UnreadBadgeManager {
/**
* @param {string} buttonId - ID кнопки скролла
*/
constructor(buttonId) {
this.buttonId = buttonId;
this.button = null;
this.badge = null;
this.count = 0;
this._init();
}
/**
* Инициализирует badge
* @private
*/
_init() {
this.button = document.getElementById(this.buttonId);
if (!this.button) {
console.warn(`[UnreadBadgeManager] Button #${this.buttonId} not found`);
return;
}
// Ищем существующий badge или создаем новый
this.badge = this.button.querySelector('.unread-badge');
if (!this.badge) {
this.badge = this._createBadge();
this.button.appendChild(this.badge);
}
console.log('[UnreadBadgeManager] Initialized');
}
/**
* Создает badge элемент
* @private
* @returns {HTMLElement}
*/
_createBadge() {
const badge = document.createElement('span');
badge.className = 'unread-badge';
badge.style.display = 'none';
badge.textContent = '0';
return badge;
}
/**
* Увеличивает счетчик непрочитанных
* @returns {number} Новое значение счетчика
*/
increment() {
if (!this.badge || !this.button) {
console.warn('[UnreadBadgeManager] Cannot increment: not initialized');
return this.count;
}
this.count++;
this.badge.textContent = this.count;
this.badge.style.display = 'flex';
this.button.classList.add('has-unread');
console.log('[UnreadBadgeManager] Incremented:', this.count);
return this.count;
}
/**
* Сбрасывает счетчик непрочитанных
*/
reset() {
if (!this.badge || !this.button) {
console.warn('[UnreadBadgeManager] Cannot reset: not initialized');
return;
}
this.count = 0;
this.badge.textContent = '0';
this.badge.style.display = 'none';
this.button.classList.remove('has-unread');
console.log('[UnreadBadgeManager] Reset');
}
/**
* Устанавливает конкретное значение счетчика
* @param {number} value - Новое значение
*/
setCount(value) {
if (!this.badge || !this.button) {
console.warn('[UnreadBadgeManager] Cannot set count: not initialized');
return;
}
this.count = Math.max(0, value);
this.badge.textContent = this.count;
if (this.count > 0) {
this.badge.style.display = 'flex';
this.button.classList.add('has-unread');
} else {
this.badge.style.display = 'none';
this.button.classList.remove('has-unread');
}
}
/**
* Получает текущий счетчик
* @returns {number}
*/
getCount() {
return this.count;
}
/**
* Проверяет есть ли непрочитанные
* @returns {boolean}
*/
hasUnread() {
return this.count > 0;
}
/**
* Уничтожает badge manager
*/
destroy() {
if (this.badge && this.badge.parentNode) {
this.badge.remove();
}
this.button = null;
this.badge = null;
this.count = 0;
}
}