const i18n = { de: { badge_realtime: "Echtzeit Mesh-Chat", label_user: "Name:", title_channels: "KANÄLE", title_online: "ONLINE", status_connected: "Verbunden", status_connecting: "Verbinde...", status_disconnected: "Getrennt", placeholder_input: "Nachricht an #{channel} schreiben...", btn_send: "Senden", modal_new_channel_title: "Neuen Kanal erstellen", label_channel_name: "Kanalname:", label_channel_topic: "Thema / Beschreibung:", btn_cancel: "Abbrechen", btn_create: "Erstellen", no_messages: "Keine Nachrichten bisher. Sei der Erste!", }, en: { badge_realtime: "Realtime Mesh Chat", label_user: "Name:", title_channels: "CHANNELS", title_online: "ONLINE", status_connected: "Connected", status_connecting: "Connecting...", status_disconnected: "Disconnected", placeholder_input: "Message #{channel}...", btn_send: "Send", modal_new_channel_title: "Create New Channel", label_channel_name: "Channel Name:", label_channel_topic: "Topic / Description:", btn_cancel: "Cancel", btn_create: "Create", no_messages: "No messages yet. Say hello!", } }; let currentLang = localStorage.getItem("benzcloud_lang") || "de"; let currentUser = localStorage.getItem("benzcloud_chat_user") || "admin"; let currentChannel = "general"; let channels = []; let onlineUsers = []; let ws = null; let reconnectTimer = null; const AVATAR_COLORS = [ "#38bdf8", "#818cf8", "#c084fc", "#f472b6", "#fb7185", "#34d399", "#4ade80", "#fbbf24", "#f87171" ]; function getAvatarColor(name) { let hash = 0; for (let i = 0; i < name.length; i++) { hash = name.charCodeAt(i) + ((hash << 5) - hash); } return AVATAR_COLORS[Math.abs(hash) % AVATAR_COLORS.length]; } function setLang(lang) { currentLang = lang; localStorage.setItem("benzcloud_lang", lang); document.documentElement.lang = lang; document.querySelectorAll("[data-i18n]").forEach(el => { const key = el.getAttribute("data-i18n"); if (i18n[lang] && i18n[lang][key]) { el.textContent = i18n[lang][key]; } }); document.getElementById("btnDe").classList.toggle("active", lang === "de"); document.getElementById("btnEn").classList.toggle("active", lang === "en"); updateInputPlaceholder(); } function updateInputPlaceholder() { const input = document.getElementById("chatInput"); input.placeholder = i18n[currentLang].placeholder_input.replace("{channel}", currentChannel); } // WebSocket Connection function connectWS() { if (ws) { ws.close(); } const proto = window.location.protocol === "https:" ? "wss:" : "ws:"; const wsUrl = `${proto}//${window.location.host}/ws?user=${encodeURIComponent(currentUser)}`; const statusEl = document.getElementById("wsStatus"); const labelEl = statusEl.querySelector(".status-label"); statusEl.className = "connection-status"; labelEl.textContent = i18n[currentLang].status_connecting; ws = new WebSocket(wsUrl); ws.onopen = () => { statusEl.className = "connection-status"; labelEl.textContent = i18n[currentLang].status_connected; if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; } }; ws.onmessage = (event) => { try { const data = JSON.parse(event.data); if (data.type === "presence") { updatePresence(data.users); } else if (data.channel) { if (data.channel.toLowerCase() === currentChannel.toLowerCase()) { appendMessage(data); } } } catch (e) { console.error("Error parsing WS message", e); } }; ws.onclose = () => { statusEl.className = "connection-status disconnected"; labelEl.textContent = i18n[currentLang].status_disconnected; if (!reconnectTimer) { reconnectTimer = setTimeout(connectWS, 3000); } }; ws.onerror = () => { ws.close(); }; } function updatePresence(users) { onlineUsers = users || []; document.getElementById("onlineCount").textContent = onlineUsers.length; const container = document.getElementById("onlineUsersList"); container.innerHTML = ""; onlineUsers.forEach(u => { const item = document.createElement("div"); item.className = "user-item"; item.innerHTML = ` ${escapeHtml(u)} `; container.appendChild(item); }); } // Channels & Messages async function loadChannels() { try { const res = await fetch("/api/chat/channels"); if (res.ok) { channels = await res.json(); renderChannels(); } } catch (err) { console.error("Failed to load channels", err); } } function renderChannels() { const list = document.getElementById("channelList"); list.innerHTML = ""; channels.forEach(ch => { const btn = document.createElement("button"); btn.className = `channel-item ${ch.name.toLowerCase() === currentChannel.toLowerCase() ? "active" : ""}`; btn.innerHTML = `# ${escapeHtml(ch.name)}`; btn.addEventListener("click", () => switchChannel(ch.name)); list.appendChild(btn); }); const activeObj = channels.find(c => c.name.toLowerCase() === currentChannel.toLowerCase()); if (activeObj) { document.getElementById("channelHeaderTitle").textContent = `#${activeObj.name}`; document.getElementById("channelHeaderTopic").textContent = activeObj.topic || ""; } updateInputPlaceholder(); } async function switchChannel(channelName) { currentChannel = channelName.toLowerCase(); renderChannels(); await loadMessages(); } async function loadMessages() { const stream = document.getElementById("messageStream"); stream.innerHTML = ""; try { const res = await fetch(`/api/chat/messages?channel=${encodeURIComponent(currentChannel)}&limit=100`); if (res.ok) { const messages = await res.json(); if (!messages || messages.length === 0) { stream.innerHTML = `