let currentUser = null; let currentPartner = null; let selectedUserElement = null; let lastMessageId = 0; let messagesInterval = null; const BASE_PATH = document.querySelector('base')?.getAttribute('href') || '/'; const API_PREFIX = BASE_PATH.replace(/\/$/, ''); // Переключение вкладок логин/регистрация function switchTab(tab) { document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); document.querySelectorAll('.auth-form').forEach(f => f.style.display = 'none'); if (tab === 'login') { document.querySelectorAll('.tab')[0].classList.add('active'); document.getElementById('login-form').style.display = 'flex'; } else { document.querySelectorAll('.tab')[1].classList.add('active'); document.getElementById('register-form').style.display = 'flex'; } } // Регистрация async function register() { const username = document.getElementById('reg-username').value.trim(); const password = document.getElementById('reg-password').value.trim(); if (!username || !password) { showAuthError('Заполните все поля'); return; } try { const res = await fetch(`${API_PREFIX}/api/register`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({username, password}) }); const data = await res.json(); if (res.ok) { showAuthError(data.message, 'green'); } else { showAuthError(data.error); } } catch (err) { showAuthError('Ошибка соединения'); } } function showAuthError(message, color = 'red') { const errorEl = document.getElementById('auth-error'); errorEl.textContent = message; errorEl.style.color = color; } // Вход async function login() { const username = document.getElementById('login-username').value.trim(); const password = document.getElementById('login-password').value.trim(); if (!username || !password) { showAuthError('Заполните все поля'); return; } try { const res = await fetch(`${API_PREFIX}/api/login`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({username, password}) }); const data = await res.json(); if (res.ok) { currentUser = data; document.getElementById('auth-screen').style.display = 'none'; document.getElementById('chat-screen').style.display = 'flex'; document.getElementById('current-username').textContent = `👤 ${data.username}`; if (data.is_admin) { document.getElementById('admin-btn').style.display = 'block'; } // На десктопе показываем сайдбар if (window.innerWidth > 768) { document.getElementById('sidebar').classList.remove('hidden'); } loadUsers(); // Запускаем проверку новых сообщений startPolling(); } else { showAuthError(data.error); } } catch (err) { showAuthError('Ошибка соединения'); } } // Загрузка списка пользователей async function loadUsers() { if (!currentUser) return; try { const res = await fetch(`${API_PREFIX}/api/users?user_id=${currentUser.user_id}`); if (!res.ok) { console.error('Error loading users'); return; } const users = await res.json(); const usersList = document.getElementById('users-list'); usersList.innerHTML = ''; if (!users || users.length === 0) { usersList.innerHTML = '
Нет активных пользователей
'; return; } users.forEach(user => { const userEl = document.createElement('div'); userEl.className = 'user-item'; userEl.innerHTML = `${escapeHtml(user.username)}`; userEl.onclick = () => selectUser(user, userEl); // Если это текущий партнер, подсвечиваем if (currentPartner && currentPartner.id === user.id) { userEl.classList.add('active'); selectedUserElement = userEl; } usersList.appendChild(userEl); }); } catch (err) { console.error('Error loading users:', err); } } // Выбор собеседника function selectUser(user, element) { if (selectedUserElement) { selectedUserElement.classList.remove('active'); } currentPartner = user; selectedUserElement = element; element.classList.add('active'); document.getElementById('chat-header').innerHTML = ` 💬 ${escapeHtml(user.username)} `; document.getElementById('message-input').disabled = false; document.getElementById('send-btn').disabled = false; // Скрываем сайдбар на мобильных if (window.innerWidth <= 768) { document.getElementById('sidebar').classList.add('hidden'); } // Сбрасываем ID последнего сообщения для нового партнера lastMessageId = 0; // Загружаем сообщения loadMessages(true); } // Загрузка сообщений с проверкой новых async function loadMessages(forceScroll = false) { if (!currentPartner || !currentUser) return; try { const res = await fetch(`${API_PREFIX}/api/messages?user_id=${currentUser.user_id}&partner_id=${currentPartner.id}`); const messages = await res.json(); if (!messages || messages.length === 0) { const messagesDiv = document.getElementById('messages'); messagesDiv.innerHTML = `
💭
Нет сообщений
Напишите первое!
`; return; } // Проверяем, есть ли новые сообщения const latestMessage = messages[messages.length - 1]; if (latestMessage.id === lastMessageId && !forceScroll) { return; // Нет новых сообщений, не обновляем DOM } lastMessageId = latestMessage.id; const messagesDiv = document.getElementById('messages'); // Проверяем, был ли пользователь внизу const isAtBottom = messagesDiv.scrollTop + messagesDiv.clientHeight >= messagesDiv.scrollHeight - 30; // Обновляем только если есть изменения messagesDiv.innerHTML = ''; messages.forEach(msg => { const msgEl = document.createElement('div'); msgEl.className = `message ${msg.from_id === currentUser.user_id ? 'sent' : 'received'}`; const time = new Date(msg.timestamp * 1000).toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' }); msgEl.innerHTML = `
${msg.from_id !== currentUser.user_id ? `
${escapeHtml(msg.username)}
` : ''} ${escapeHtml(msg.text)}
${time}
`; messagesDiv.appendChild(msgEl); }); // Скроллим вниз только если были внизу или это принудительно if (isAtBottom || forceScroll) { messagesDiv.scrollTop = messagesDiv.scrollHeight; } } catch (err) { console.error('Error loading messages:', err); } } // Защита от XSS function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Отправка сообщения async function sendMessage() { const input = document.getElementById('message-input'); const text = input.value.trim(); if (!text || !currentPartner) return; // Блокируем кнопку на время отправки const sendBtn = document.getElementById('send-btn'); sendBtn.disabled = true; input.disabled = true; try { const res = await fetch(`${API_PREFIX}/api/messages`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ from_id: currentUser.user_id, to_id: currentPartner.id, text: text }) }); if (res.ok) { input.value = ''; // Принудительно обновляем сообщения await loadMessages(true); } } catch (err) { console.error('Error sending message:', err); } finally { sendBtn.disabled = false; input.disabled = false; input.focus(); } } // Умный поллинг function startPolling() { if (messagesInterval) { clearInterval(messagesInterval); } // Проверяем новые сообщения каждые 2 секунды messagesInterval = setInterval(() => { if (currentPartner) { loadMessages(false); } }, 2000); // Также проверяем список пользователей раз в 30 секунд setInterval(() => { if (currentUser) { loadUsers(); } }, 30000); } // Показать боковую панель на мобильных function showSidebar() { if (window.innerWidth <= 768) { document.getElementById('sidebar').classList.remove('hidden'); } } // Enter для отправки document.addEventListener('DOMContentLoaded', () => { const messageInput = document.getElementById('message-input'); messageInput.addEventListener('keypress', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); // Фикс для мобильных устройств - поднятие поля ввода при фокусе messageInput.addEventListener('focus', () => { setTimeout(() => { const messagesDiv = document.getElementById('messages'); messagesDiv.scrollTop = messagesDiv.scrollHeight; messageInput.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }, 300); }); messageInput.addEventListener('blur', () => { window.scrollTo(0, 0); }); }); // Обработка изменения размера окна let resizeTimeout; window.addEventListener('resize', () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => { if (window.innerWidth > 768) { document.getElementById('sidebar').classList.remove('hidden'); } else if (!currentPartner) { document.getElementById('sidebar').classList.remove('hidden'); } }, 250); }); // Админ панель async function showAdminPanel() { const panel = document.getElementById('admin-panel'); panel.style.display = 'flex'; try { const res = await fetch(`${API_PREFIX}/api/admin/users?admin_id=${currentUser.user_id}`); const users = await res.json(); const adminList = document.getElementById('admin-users-list'); adminList.innerHTML = '

Все зарегистрированные пользователи:

'; users.forEach(user => { const userEl = document.createElement('div'); userEl.className = 'admin-user-item'; const statusClass = user.active ? 'status-active' : 'status-inactive'; const statusText = user.active ? 'Активен' : 'Неактивен'; userEl.innerHTML = `
${escapeHtml(user.username)} ${user.id === currentUser.user_id ? '(вы)' : ''} ${statusText}
${user.id !== currentUser.user_id ? ` ` : ''}
`; adminList.appendChild(userEl); }); } catch (err) { console.error('Error loading admin panel:', err); } } function closeAdminPanel() { document.getElementById('admin-panel').style.display = 'none'; } async function toggleUser(userId, active) { try { await fetch(`${API_PREFIX}/api/admin/toggle`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ admin_id: currentUser.user_id, user_id: userId, active: active }) }); // Обновляем админ-панель и список пользователей await showAdminPanel(); await loadUsers(); } catch (err) { console.error('Error toggling user:', err); } }