Corp/backend/static/js/pages/chatDetailV2.js
Игорь Надеин d5be62b34c refactor: восстановлены старые стили пузырей сообщений, аватаров и разделителей (pre-redesign)
- Восстановлен мягкий цвет пузырей: color-mix(92%) вместо жесткого var(--bs-primary)
- Аватары: border-radius 9999px + grid layout для идеальных кругов
- Разделители: правильный spacing, font-weight 700, pointer-events, color-mix прозрачность
- Добавлены оптимизации скролла: rootMargin 300px, limit 50 сообщений (Telegram-стиль)
- Добавлены новые модули: messageLoader, scrollManager, chatUtils
- Рефакторинг логики загрузки сообщений для предотвращения дубликатов
- Создана документация по архитектуре и тестированию
2026-01-12 17:21:41 +03:00

232 lines
8.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 Chat Detail Page V2 - рефакторированная инициализация чата
* @module pages/chatDetailV2
*
* НОВАЯ АРХИТЕКТУРА V2:
* - Использует ChatControllerV2 с чистой архитектурой
* - MessageStoreV2 с бинарным поиском
* - MessageLoaderV2 с retry-логикой
* - ScrollManagerV2 с debounce/throttle
* - Минимум логирования
*/
(async function() {
'use strict';
// ==================== Загрузка модулей ====================
let ChatControllerV2, initChatMarkRead, initChatComposer, initChatFormManager;
try {
const [
controllerModule,
markReadModule,
composerModule,
formManagerModule
] = await Promise.all([
import('../controllers/chatControllerV2.js'),
import('../components/chatMarkRead.js'),
import('../components/chatComposer.js'),
import('../components/chatFormManager.js')
]);
ChatControllerV2 = controllerModule.ChatControllerV2;
initChatMarkRead = markReadModule.initChatMarkRead;
initChatComposer = composerModule.initChatComposer;
initChatFormManager = formManagerModule.initChatFormManager;
if (!ChatControllerV2) {
throw new Error('ChatControllerV2 is undefined');
}
} catch (error) {
console.error('[ChatDetailV2] Failed to load modules:', error);
return;
}
// ==================== Конфигурация ====================
/**
* Читает конфигурацию из DOM
* @returns {Object|null}
*/
function readConfig() {
const appContainer = document.getElementById('chatDetailApp');
if (!appContainer) {
console.warn('[ChatDetailV2] App container not found');
return null;
}
const scrollContainer = document.getElementById('chatScroll');
return {
chatId: Number(appContainer.dataset.chatId),
userId: Number(appContainer.dataset.userId),
userName: appContainer.dataset.userName || 'Вы',
userAvatar: appContainer.dataset.userAvatar || '',
uploadUrl: appContainer.dataset.uploadUrl,
messagesUrl: appContainer.dataset.messagesUrl,
markReadUrl: appContainer.dataset.markReadUrl,
editUrlTemplate: appContainer.dataset.editUrlTemplate,
lastReadMessageId: appContainer.dataset.lastReadMessageId
? Number(appContainer.dataset.lastReadMessageId)
: null,
profileUrl: scrollContainer?.dataset.profileUrl || '/employees/profile/',
detailUrlTemplate: scrollContainer?.dataset.detailUrlTemplate || '/employees/detail/0/'
};
}
/**
* Ждет готовности WebSocket
* @param {number} timeout - Таймаут в мс
* @returns {Promise<Object>}
*/
function waitForWebSocket(timeout = 10000) {
return new Promise((resolve, reject) => {
// Уже готов
if (window.userWebSocket?.ws) {
resolve(window.userWebSocket);
return;
}
let timeoutId;
const handler = (e) => {
clearTimeout(timeoutId);
resolve(e.detail.api);
};
window.addEventListener('user:ws-ready', handler, { once: true });
timeoutId = setTimeout(() => {
window.removeEventListener('user:ws-ready', handler);
reject(new Error('WebSocket connection timeout'));
}, timeout);
});
}
// ==================== Инициализация компонентов ====================
/**
* Инициализирует все компоненты чата
* @param {Object} config - Конфигурация
* @param {Object} wsApi - WebSocket API
*/
async function initializeComponents(config, wsApi) {
const scrollElement = document.getElementById('chatScroll');
if (!scrollElement) {
console.error('[ChatDetailV2] Scroll container not found');
return;
}
// 0. Открываем чат в WebSocket ДО инициализации
if (wsApi?.openChat) {
wsApi.openChat(config.chatId, false);
}
// 1. Chat Controller (главный координатор)
const chatController = new ChatControllerV2({
chatId: config.chatId,
currentUserId: config.userId,
scrollElement,
containerId: 'chatScroll',
wsConnection: wsApi,
profileUrl: config.profileUrl,
detailUrlTemplate: config.detailUrlTemplate,
lastReadMessageId: config.lastReadMessageId
});
await chatController.init();
console.log('[ChatDetailV2] ChatController initialized');
// 2. Form Manager (редактирование сообщений)
const formManager = initChatFormManager({
formId: 'chatForm',
textareaId: 'id_content',
chatId: config.chatId,
uploadUrl: config.uploadUrl,
editUrlTemplate: config.editUrlTemplate
});
// 3. Mark Read (отслеживание прочитанных)
const markReadApi = initChatMarkRead({
chatId: config.chatId,
meId: config.userId,
scrollContainerId: 'chatScroll',
textareaId: 'id_content',
formId: 'chatForm',
scrollBtnId: 'scrollBtn',
markReadUrl: config.markReadUrl,
initialLastReadTs: config.lastReadMessageId
});
// 4. Composer (отправка сообщений)
const composer = initChatComposer({
chatId: config.chatId,
formId: 'chatForm',
textareaId: 'id_content',
uploadUrl: config.uploadUrl,
meId: config.userId,
meName: config.userName,
meAvatar: config.userAvatar,
chatController
});
// 5. Экспорт в window
window.chatController = chatController;
window.chatComposer = composer;
window.markReadApi = markReadApi;
window.chatFormManager = formManager;
// Backward compatibility
window.messageRenderer = {
renderMessage: () => console.warn('[DEPRECATED] Use chatController'),
renderMessages: () => console.warn('[DEPRECATED] Use chatController')
};
window.chatHistoryLoader = {
loadMore: () => chatController.loadMoreHistory()
};
console.log('[ChatDetailV2] All components initialized');
}
// ==================== Entry Point ====================
/**
* Главная функция инициализации
*/
async function init() {
const config = readConfig();
if (!config || !config.chatId) {
console.error('[ChatDetailV2] Invalid configuration');
return;
}
try {
const wsApi = await waitForWebSocket();
await initializeComponents(config, wsApi);
} catch (error) {
console.error('[ChatDetailV2] Initialization failed:', error);
// Показываем ошибку пользователю
const scrollEl = document.getElementById('chatScroll');
if (scrollEl) {
scrollEl.innerHTML = `
<div class="alert alert-danger m-3">
<i class="bi bi-exclamation-triangle me-2"></i>
Ошибка загрузки чата.
<a href="#" onclick="location.reload()">Обновить страницу</a>
</div>
`;
}
}
}
// Запуск
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();