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