Sonniges UI und stabilere Chat-Streams

This commit is contained in:
2025-11-03 18:31:06 +01:00
parent 0ffbf9b8af
commit ddf9691a8c
+373 -85
View File
@@ -1470,14 +1470,30 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
box-sizing: border-box; box-sizing: border-box;
} }
:root {
--sun-50: #fff9db;
--sun-100: #fef3c7;
--sun-200: #fde68a;
--sun-300: #fcd34d;
--sun-400: #fbbf24;
--sun-500: #f59e0b;
--sun-600: #d97706;
--sun-700: #b45309;
--sun-800: #92400e;
--sun-900: #78350f;
--text-dark: #3d2c00;
--text-muted: rgba(61, 44, 0, 0.7);
}
body { body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #fef08a 0%, #f97316 100%);
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 20px; padding: 20px;
color: var(--text-dark);
} }
/* ═══════════════════════════════════════════════════════════ */ /* ═══════════════════════════════════════════════════════════ */
@@ -1485,7 +1501,7 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
/* ═══════════════════════════════════════════════════════════ */ /* ═══════════════════════════════════════════════════════════ */
.auth-container { .auth-container {
background: white; background: #fff9db;
padding: 40px; padding: 40px;
border-radius: 20px; border-radius: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3); box-shadow: 0 20px 60px rgba(0,0,0,0.3);
@@ -1494,35 +1510,35 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
} }
.auth-container h1 { .auth-container h1 {
color: #667eea; color: #d97706;
margin-bottom: 10px; margin-bottom: 10px;
font-size: 32px; font-size: 32px;
text-align: center; text-align: center;
} }
.auth-container .subtitle { .auth-container .subtitle {
color: #666; color: #7c4a03;
margin-bottom: 30px; margin-bottom: 30px;
text-align: center; text-align: center;
font-size: 14px; font-size: 14px;
} }
.auth-container .warning-box { .auth-container .warning-box {
background: #fff3cd; background: #fef3c7;
border: 2px solid #ffc107; border: 2px solid #fbbf24;
border-radius: 10px; border-radius: 10px;
padding: 15px; padding: 15px;
margin-bottom: 20px; margin-bottom: 20px;
} }
.auth-container .warning-box h3 { .auth-container .warning-box h3 {
color: #856404; color: #a16207;
margin-bottom: 10px; margin-bottom: 10px;
font-size: 16px; font-size: 16px;
} }
.auth-container .warning-box ul { .auth-container .warning-box ul {
color: #856404; color: #a16207;
margin-left: 20px; margin-left: 20px;
font-size: 13px; font-size: 13px;
line-height: 1.6; line-height: 1.6;
@@ -1534,7 +1550,7 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
.auth-container label { .auth-container label {
display: block; display: block;
color: #333; color: #7c4a03;
font-weight: 600; font-weight: 600;
margin-bottom: 8px; margin-bottom: 8px;
font-size: 14px; font-size: 14px;
@@ -1545,15 +1561,16 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
.auth-container input[type="password"] { .auth-container input[type="password"] {
width: 100%; width: 100%;
padding: 12px 15px; padding: 12px 15px;
border: 2px solid #e0e0e0; border: 2px solid #fde68a;
border-radius: 10px; border-radius: 10px;
font-size: 15px; font-size: 15px;
transition: border-color 0.3s; transition: border-color 0.3s, box-shadow 0.3s;
} }
.auth-container input:focus { .auth-container input:focus {
outline: none; outline: none;
border-color: #667eea; border-color: #f59e0b;
box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.25);
} }
.auth-container .checkbox-group { .auth-container .checkbox-group {
@@ -1579,11 +1596,11 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
.auth-container .terms-text { .auth-container .terms-text {
font-size: 12px; font-size: 12px;
color: #666; color: #7c4a03;
line-height: 1.6; line-height: 1.6;
margin-top: 10px; margin-top: 10px;
padding: 10px; padding: 10px;
background: #f8f9fa; background: #fff4cc;
border-radius: 5px; border-radius: 5px;
max-height: 150px; max-height: 150px;
overflow-y: auto; overflow-y: auto;
@@ -1592,18 +1609,19 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
.auth-container button { .auth-container button {
width: 100%; width: 100%;
padding: 15px; padding: 15px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #fbbf24 0%, #f97316 100%);
color: white; color: #3d2c00;
border: none; border: none;
border-radius: 10px; border-radius: 10px;
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
cursor: pointer; cursor: pointer;
transition: transform 0.2s; transition: transform 0.2s, box-shadow 0.2s;
} }
.auth-container button:hover { .auth-container button:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 12px 24px rgba(249, 115, 22, 0.25);
} }
.auth-container button:disabled { .auth-container button:disabled {
@@ -1616,24 +1634,25 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
/* ═══════════════════════════════════════════════════════════ */ /* ═══════════════════════════════════════════════════════════ */
.admin-login-container { .admin-login-container {
background: white; background: var(--sun-50);
padding: 40px; padding: 40px;
border-radius: 20px; border-radius: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3); box-shadow: 0 20px 60px rgba(120, 53, 15, 0.25);
width: 100%; width: 100%;
max-width: 450px; max-width: 450px;
color: var(--text-dark);
} }
.admin-login-container h1 { .admin-login-container h1 {
text-align: center; text-align: center;
font-size: 28px; font-size: 28px;
margin-bottom: 10px; margin-bottom: 10px;
color: #4c51bf; color: var(--sun-800);
} }
.admin-login-container p { .admin-login-container p {
text-align: center; text-align: center;
color: #666; color: var(--text-muted);
margin-bottom: 25px; margin-bottom: 25px;
} }
@@ -1645,21 +1664,22 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
display: block; display: block;
margin-bottom: 6px; margin-bottom: 6px;
font-weight: 600; font-weight: 600;
color: #333; color: var(--sun-800);
} }
.admin-login-container input { .admin-login-container input {
width: 100%; width: 100%;
padding: 12px 14px; padding: 12px 14px;
border-radius: 10px; border-radius: 10px;
border: 2px solid #e0e0e0; border: 2px solid var(--sun-200);
font-size: 15px; font-size: 15px;
transition: border-color 0.2s ease; transition: border-color 0.2s ease, box-shadow 0.2s ease;
} }
.admin-login-container input:focus { .admin-login-container input:focus {
outline: none; outline: none;
border-color: #667eea; border-color: var(--sun-600);
box-shadow: 0 0 0 3px rgba(217, 119, 6, 0.2);
} }
.admin-login-container button { .admin-login-container button {
@@ -1667,16 +1687,17 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
padding: 14px; padding: 14px;
border: none; border: none;
border-radius: 10px; border-radius: 10px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, var(--sun-400) 0%, var(--sun-600) 100%);
color: white; color: var(--text-dark);
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
cursor: pointer; cursor: pointer;
transition: transform 0.2s ease; transition: transform 0.2s ease, box-shadow 0.2s ease;
} }
.admin-login-container button:hover { .admin-login-container button:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 12px 24px rgba(217, 119, 6, 0.28);
} }
.admin-login-container .back-link { .admin-login-container .back-link {
@@ -1685,7 +1706,7 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
} }
.admin-login-container .back-link a { .admin-login-container .back-link a {
color: #667eea; color: var(--sun-700);
text-decoration: none; text-decoration: none;
font-weight: 600; font-weight: 600;
} }
@@ -1697,13 +1718,14 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
.admin-dashboard { .admin-dashboard {
width: 95%; width: 95%;
max-width: 1400px; max-width: 1400px;
background: white; background: var(--sun-50);
border-radius: 20px; border-radius: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3); box-shadow: 0 20px 60px rgba(120, 53, 15, 0.25);
padding: 30px; padding: 30px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 30px; gap: 30px;
color: var(--text-dark);
} }
.admin-dashboard-header { .admin-dashboard-header {
@@ -1715,21 +1737,24 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
.admin-dashboard-header h1 { .admin-dashboard-header h1 {
font-size: 26px; font-size: 26px;
color: #4c51bf; color: var(--sun-800);
} }
.admin-dashboard-header button { .admin-dashboard-header button {
padding: 10px 18px; padding: 10px 18px;
border: none; border: none;
border-radius: 8px; border-radius: 8px;
background: #ef4444; background: linear-gradient(135deg, #f87171 0%, #ef4444 100%);
color: white; color: white;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
box-shadow: 0 10px 24px rgba(239, 68, 68, 0.35);
transition: transform 0.2s ease, box-shadow 0.2s ease;
} }
.admin-dashboard-header button:hover { .admin-dashboard-header button:hover {
background: #dc2626; transform: translateY(-1px);
box-shadow: 0 12px 28px rgba(220, 38, 38, 0.4);
} }
.admin-stats-grid { .admin-stats-grid {
@@ -1741,12 +1766,12 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
.admin-stat-card { .admin-stat-card {
padding: 20px; padding: 20px;
border-radius: 16px; border-radius: 16px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, var(--sun-400) 0%, var(--sun-700) 100%);
color: white; color: var(--text-dark);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 6px; gap: 6px;
box-shadow: 0 12px 30px rgba(102, 126, 234, 0.35); box-shadow: 0 12px 30px rgba(250, 204, 21, 0.35);
} }
.admin-stat-card span { .admin-stat-card span {
@@ -1766,16 +1791,17 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
} }
.admin-section { .admin-section {
background: #f9fafb; background: rgba(255, 255, 255, 0.9);
border-radius: 16px; border-radius: 16px;
padding: 20px; padding: 20px;
border: 1px solid #e5e7eb; border: 1px solid rgba(180, 83, 9, 0.2);
box-shadow: inset 0 0 0 1px rgba(255, 200, 92, 0.25);
} }
.admin-section h2 { .admin-section h2 {
font-size: 18px; font-size: 18px;
margin-bottom: 15px; margin-bottom: 15px;
color: #1f2937; color: var(--sun-700);
} }
.admin-table-wrapper { .admin-table-wrapper {
@@ -1799,7 +1825,7 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
font-size: 13px; font-size: 13px;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.05em; letter-spacing: 0.05em;
color: #6b7280; color: var(--sun-700);
} }
.admin-action-buttons { .admin-action-buttons {
@@ -2267,7 +2293,8 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
.empty-user-list, .empty-user-list,
.empty-messages, .empty-messages,
.loading-state, .loading-state,
.error-state { .error-state,
.chat-state-message {
text-align: center; text-align: center;
padding: 30px 20px; padding: 30px 20px;
color: rgba(60, 42, 0, 0.6); color: rgba(60, 42, 0, 0.6);
@@ -2285,6 +2312,22 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
.error-state { .error-state {
color: #c2410c; color: #c2410c;
} }
.chat-state-message.hidden {
display: none;
}
.chat-state-message.loading-state {
font-style: italic;
}
.chat-state-message.error-state {
color: #c2410c;
}
.hidden {
display: none !important;
}
</style> </style>
</head> </head>
<body> <body>
@@ -2430,6 +2473,7 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
<!-- Populated via JS --> <!-- Populated via JS -->
</div> </div>
<div class="chat-state-message hidden" id="chatStateMessage"></div>
<div class="chat-messages" id="chatMessages"> <div class="chat-messages" id="chatMessages">
<!-- Messages loaded via JS --> <!-- Messages loaded via JS -->
</div> </div>
@@ -2798,72 +2842,265 @@ const state = {
users: [], users: [],
messages: [], messages: [],
lastMessageId: 0, lastMessageId: 0,
eventSource: null eventSource: null,
isLoadingUsers: false,
isLoadingMessages: false,
connectionErrorShown: false
}; };
const userListEl = document.getElementById('userList');
const userSearchInput = document.getElementById('userSearch');
const chatWelcomeEl = document.getElementById('chatWelcome');
const chatMessagesContainerEl = document.getElementById('chatMessagesContainer');
const chatMessagesEl = document.getElementById('chatMessages');
const chatStateMessageEl = document.getElementById('chatStateMessage');
const chatMessagesHeaderEl = document.getElementById('chatMessagesHeader');
const chatInputEl = document.getElementById('chatInput');
const sendButtonEl = document.getElementById('sendButton');
async function loadUsers() { async function loadUsers() {
if (!userListEl) {
return;
}
state.isLoadingUsers = true;
renderUserList();
try {
const response = await fetch('?action=get_users'); const response = await fetch('?action=get_users');
if (!response.ok) {
throw new Error('NETZWERK_FEHLER');
}
const result = await response.json(); const result = await response.json();
if (result.success) { if (result.success) {
state.users = result.users; state.users = Array.isArray(result.users) ? result.users : [];
} else {
throw new Error(result.error || 'Nutzerliste konnte nicht geladen werden.');
}
state.isLoadingUsers = false;
renderUserList(); renderUserList();
} catch (error) {
console.error('Nutzerliste konnte nicht geladen werden:', error);
state.isLoadingUsers = false;
if (userListEl) {
userListEl.innerHTML = '<div class="error-state">Nutzerliste konnte nicht geladen werden.</div>';
}
} }
} }
function renderUserList() { function renderUserList() {
const userList = document.getElementById('userList'); if (!userListEl) {
const searchTerm = document.getElementById('userSearch').value.toLowerCase(); return;
}
const filtered = state.users.filter(u => u.display_name.toLowerCase().includes(searchTerm)); const searchTerm = (userSearchInput?.value || '').toLowerCase();
const users = Array.isArray(state.users) ? state.users : [];
userList.innerHTML = filtered.map(user => ` if (state.isLoadingUsers && users.length === 0) {
<div class="user-item ${user.id === state.selectedUserId ? 'active' : ''}" onclick="selectUser(${user.id}, '${user.display_name}')"> userListEl.innerHTML = '<div class="loading-state">Nutzer werden geladen…</div>';
<div class="user-avatar"> return;
${user.username.charAt(0).toUpperCase()} }
<div class="online-indicator ${user.is_online ? '' : 'offline-indicator'}"></div>
</div> if (users.length === 0) {
<div class="user-info-text"> userListEl.innerHTML = '<div class="empty-user-list">Noch keine passenden Kontakte verfügbar.</div>';
<div class="user-name">${user.display_name}</div> return;
<div class="user-status">${user.is_online ? 'Online' : 'Offline'}</div> }
</div>
${user.unread_count > 0 ? `<div class="unread-badge">${user.unread_count}</div>` : ''} const filtered = users.filter(user => user.display_name.toLowerCase().includes(searchTerm));
</div>
`).join(''); if (filtered.length === 0) {
userListEl.innerHTML = '<div class="empty-user-list">Keine Treffer für deine Suche.</div>';
return;
}
const fragment = document.createDocumentFragment();
filtered.forEach(user => {
const item = document.createElement('button');
item.type = 'button';
item.className = 'user-item' + (Number(user.id) === Number(state.selectedUserId) ? ' active' : '');
item.dataset.userId = String(user.id);
item.dataset.displayName = user.display_name;
const avatar = document.createElement('div');
avatar.className = 'user-avatar';
avatar.textContent = (user.username || '?').charAt(0).toUpperCase();
const indicator = document.createElement('div');
indicator.className = 'online-indicator' + (user.is_online ? '' : ' offline-indicator');
avatar.appendChild(indicator);
const infoWrapper = document.createElement('div');
infoWrapper.className = 'user-info-text';
const name = document.createElement('div');
name.className = 'user-name';
name.textContent = user.display_name;
const status = document.createElement('div');
status.className = 'user-status';
status.textContent = user.is_online ? 'Online' : 'Offline';
infoWrapper.appendChild(name);
infoWrapper.appendChild(status);
item.appendChild(avatar);
item.appendChild(infoWrapper);
if (Number(user.unread_count) > 0) {
const unread = document.createElement('div');
unread.className = 'unread-badge';
unread.textContent = String(user.unread_count);
item.appendChild(unread);
}
item.addEventListener('click', () => {
selectUser(Number(user.id), user.display_name);
});
fragment.appendChild(item);
});
userListEl.innerHTML = '';
userListEl.appendChild(fragment);
}
function renderChatHeader(displayName) {
if (!chatMessagesHeaderEl) {
return;
}
chatMessagesHeaderEl.innerHTML = '';
const avatar = document.createElement('div');
avatar.className = 'user-avatar';
const initial = (displayName?.trim() || '?').charAt(0).toUpperCase();
avatar.textContent = initial || '?';
const info = document.createElement('div');
const name = document.createElement('div');
name.className = 'user-name';
name.textContent = displayName;
info.appendChild(name);
chatMessagesHeaderEl.appendChild(avatar);
chatMessagesHeaderEl.appendChild(info);
}
function updateChatState(type, message = '') {
if (!chatStateMessageEl || !chatMessagesEl) {
return;
}
chatStateMessageEl.className = 'chat-state-message';
if (!type) {
chatStateMessageEl.textContent = '';
chatStateMessageEl.classList.add('hidden');
chatMessagesEl.classList.remove('hidden');
return;
}
chatStateMessageEl.textContent = message;
chatStateMessageEl.classList.remove('hidden');
if (type === 'loading') {
chatStateMessageEl.classList.add('loading-state');
} else if (type === 'error') {
chatStateMessageEl.classList.add('error-state');
} else if (type === 'empty') {
chatStateMessageEl.classList.add('empty-messages');
}
const hideMessages = type === 'loading' || type === 'error' || type === 'empty';
chatMessagesEl.classList.toggle('hidden', hideMessages);
} }
function selectUser(userId, displayName) { function selectUser(userId, displayName) {
state.selectedUserId = userId; state.selectedUserId = userId;
state.messages = [];
document.getElementById('chatWelcome').style.display = 'none'; if (chatWelcomeEl) {
document.getElementById('chatMessagesContainer').style.display = 'flex'; chatWelcomeEl.style.display = 'none';
}
document.getElementById('chatMessagesHeader').innerHTML = ` if (chatMessagesContainerEl) {
<div class="user-avatar">${displayName.charAt(0).toUpperCase()}</div> chatMessagesContainerEl.style.display = 'flex';
<div><div class="user-name">${displayName}</div></div> }
`;
loadMessages(userId); if (chatMessagesEl) {
chatMessagesEl.innerHTML = '';
chatMessagesEl.classList.add('hidden');
}
renderChatHeader(displayName);
updateChatState('loading', 'Nachrichten werden geladen…');
renderUserList(); renderUserList();
loadMessages(userId);
} }
async function loadMessages(userId) { async function loadMessages(userId) {
if (!userId) {
return;
}
state.isLoadingMessages = true;
updateChatState('loading', 'Nachrichten werden geladen…');
try {
const response = await fetch(`?action=get_messages&user_id=${userId}`); const response = await fetch(`?action=get_messages&user_id=${userId}`);
if (!response.ok) {
throw new Error('NETZWERK_FEHLER');
}
const result = await response.json(); const result = await response.json();
if (result.success) { if (!result.success) {
state.messages = result.messages; throw new Error(result.error || 'Nachrichten konnten nicht geladen werden.');
}
state.messages = Array.isArray(result.messages) ? result.messages : [];
if (state.messages.length > 0) {
renderMessages(); renderMessages();
markAsRead(userId); markAsRead(userId);
const newLastMessageId = Math.max(...state.messages.map(m => Number(m.id)));
if (result.messages.length > 0) { state.lastMessageId = Math.max(state.lastMessageId, newLastMessageId);
state.lastMessageId = Math.max(...result.messages.map(m => m.id)); } else {
if (chatMessagesEl) {
chatMessagesEl.innerHTML = '';
} }
updateChatState('empty', 'Noch keine Nachrichten. Starte das Gespräch!');
}
} catch (error) {
console.error('Nachrichten konnten nicht geladen werden:', error);
state.messages = [];
if (chatMessagesEl) {
chatMessagesEl.innerHTML = '';
}
const errorMessage = (error && error.message && error.message !== 'NETZWERK_FEHLER')
? error.message
: 'Nachrichten konnten nicht geladen werden. Bitte versuche es erneut.';
updateChatState('error', errorMessage);
} finally {
state.isLoadingMessages = false;
} }
} }
function renderMessages() { function renderMessages() {
const container = document.getElementById('chatMessages'); const container = chatMessagesEl;
if (!container) {
return;
}
if (!Array.isArray(state.messages) || state.messages.length === 0) {
container.innerHTML = '';
return;
}
container.innerHTML = state.messages.map(msg => { container.innerHTML = state.messages.map(msg => {
const isSent = msg.from_user_id === state.currentUserId; const isSent = msg.from_user_id === state.currentUserId;
@@ -2877,12 +3114,17 @@ function renderMessages() {
`; `;
}).join(''); }).join('');
container.classList.remove('hidden');
container.scrollTop = container.scrollHeight; container.scrollTop = container.scrollHeight;
updateChatState(null);
} }
async function sendMessage() { async function sendMessage() {
const input = document.getElementById('chatInput'); if (!chatInputEl) {
const message = input.value.trim(); return;
}
const message = chatInputEl.value.trim();
if (!message || !state.selectedUserId) return; if (!message || !state.selectedUserId) return;
@@ -2895,7 +3137,8 @@ async function sendMessage() {
const result = await response.json(); const result = await response.json();
if (result.success) { if (result.success) {
input.value = ''; chatInputEl.value = '';
chatInputEl.dispatchEvent(new Event('input'));
} else { } else {
alert(result.error); alert(result.error);
} }
@@ -2911,21 +3154,47 @@ async function markAsRead(userId) {
} }
function startSSE() { function startSSE() {
state.eventSource = new EventSource(`?stream=events&last_message_id=${state.lastMessageId}`); if (state.eventSource) {
state.eventSource.close();
}
const url = `?stream=events&last_message_id=${state.lastMessageId}&t=${Date.now()}`;
state.eventSource = new EventSource(url);
state.eventSource.onopen = () => {
state.connectionErrorShown = false;
if (!state.selectedUserId) {
return;
}
if (state.isLoadingMessages) {
return;
}
if (state.messages.length === 0) {
updateChatState('empty', 'Noch keine Nachrichten. Starte das Gespräch!');
} else {
updateChatState(null);
}
};
state.eventSource.onmessage = (event) => { state.eventSource.onmessage = (event) => {
state.connectionErrorShown = false;
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
if (data.type === 'messages' && data.messages) { if (data.type === 'messages' && data.messages) {
data.messages.forEach(msg => { data.messages.forEach(msg => {
if (msg.id > state.lastMessageId) { const messageId = Number(msg.id);
state.lastMessageId = msg.id;
if (messageId > state.lastMessageId) {
state.lastMessageId = messageId;
if (state.selectedUserId && if (state.selectedUserId &&
((msg.from_user_id === state.selectedUserId && msg.to_user_id === state.currentUserId) || ((msg.from_user_id === state.selectedUserId && msg.to_user_id === state.currentUserId) ||
(msg.from_user_id === state.currentUserId && msg.to_user_id === state.selectedUserId))) { (msg.from_user_id === state.currentUserId && msg.to_user_id === state.selectedUserId))) {
if (!state.messages.find(m => m.id === msg.id)) { if (!state.messages.find(m => Number(m.id) === messageId)) {
state.messages.push(msg); state.messages.push(msg);
renderMessages(); renderMessages();
@@ -2940,6 +3209,22 @@ function startSSE() {
loadUsers(); loadUsers();
} }
}; };
state.eventSource.onerror = () => {
if (!state.connectionErrorShown) {
state.connectionErrorShown = true;
console.warn('SSE-Verbindung unterbrochen, versuche Neuverbindung.');
if (state.selectedUserId && !state.isLoadingMessages) {
updateChatState('error', 'Live-Verbindung unterbrochen. Erneuter Verbindungsversuch…');
}
}
if (state.eventSource) {
state.eventSource.close();
}
setTimeout(startSSE, 1500);
};
} }
function escapeHtml(text) { function escapeHtml(text) {
@@ -2948,22 +3233,25 @@ function escapeHtml(text) {
return div.innerHTML; return div.innerHTML;
} }
document.getElementById('sendButton').addEventListener('click', sendMessage); sendButtonEl?.addEventListener('click', sendMessage);
document.getElementById('chatInput').addEventListener('keypress', (e) => {
chatInputEl?.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) { if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault(); e.preventDefault();
sendMessage(); sendMessage();
} }
}); });
document.getElementById('userSearch').addEventListener('input', renderUserList);
document.getElementById('logoutBtn').addEventListener('click', async () => { userSearchInput?.addEventListener('input', () => renderUserList());
document.getElementById('logoutBtn')?.addEventListener('click', async () => {
const formData = new FormData(); const formData = new FormData();
formData.append('action', 'logout'); formData.append('action', 'logout');
await fetch('', { method: 'POST', body: formData }); await fetch('', { method: 'POST', body: formData });
window.location.reload(); window.location.reload();
}); });
document.getElementById('chatInput').addEventListener('input', function() { chatInputEl?.addEventListener('input', function() {
this.style.height = 'auto'; this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 100) + 'px'; this.style.height = Math.min(this.scrollHeight, 100) + 'px';
}); });