import * as chatClient from "./chat-client.js"; let panelOpen = false; let initialized = false; let bubble = null; let panel = null; let messageList = null; let inputArea = null; let sendBtn = null; let muteDropdown = null; let muteDropdownMenu = null; let muteDropdownLabel = null; let oldestTimestamp = null; let loadingMore = false; let hasMore = true; const ROLE_COLORS = { admin: "bg-rose-500/80 text-white", operator: "bg-sky-500/80 text-white", viewer: "bg-zinc-500/80 text-white", }; function escapeHtml(str) { const div = document.createElement("div"); div.textContent = str; return div.innerHTML; } function formatTime(ts) { const d = new Date(ts); const now = new Date(); const sameDay = d.toDateString() === now.toDateString(); if (sameDay) { return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); } return d.toLocaleDateString([], { month: "short", day: "numeric" }) + " " + d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); } function createMessageEl(msg) { const wrapper = document.createElement("div"); wrapper.className = "flex flex-col gap-0.5 px-3 py-1.5 hover:bg-zinc-800/40 transition-colors"; wrapper.dataset.msgId = msg.id; wrapper.dataset.ts = msg.createdAt; const header = document.createElement("div"); header.className = "flex items-center gap-2 text-xs"; const badge = document.createElement("span"); badge.className = `px-1.5 py-0.5 rounded text-[10px] font-medium ${ROLE_COLORS[msg.userRole] || ROLE_COLORS.viewer}`; badge.textContent = msg.userRole; const name = document.createElement("span"); name.className = "font-semibold text-slate-200"; name.textContent = msg.username; const onlineCount = typeof msg.onlineClients === "number" ? msg.onlineClients : 0; const clientsBadge = document.createElement("span"); clientsBadge.className = "px-1.5 py-0.5 rounded text-[10px] font-medium " + (onlineCount > 0 ? "bg-emerald-500/20 text-emerald-400 border border-emerald-500/30" : "bg-zinc-700/50 text-zinc-500"); clientsBadge.textContent = onlineCount > 0 ? `${onlineCount} online` : "0 online"; const time = document.createElement("span"); time.className = "text-slate-500 ml-auto whitespace-nowrap"; time.textContent = formatTime(msg.createdAt); header.appendChild(badge); header.appendChild(name); header.appendChild(clientsBadge); header.appendChild(time); const body = document.createElement("div"); body.className = "text-sm text-slate-300 break-words whitespace-pre-wrap pl-0.5"; body.textContent = msg.message; wrapper.appendChild(header); wrapper.appendChild(body); return wrapper; } function scrollToBottom() { if (messageList) { messageList.scrollTop = messageList.scrollHeight; } } function updateBubbleBadge() { if (!bubble) return; const count = chatClient.getUnreadCount(); const badge = bubble.querySelector("[data-chat-badge]"); if (!badge) return; if (count > 0) { badge.textContent = count > 99 ? "99+" : String(count); badge.classList.remove("hidden"); } else { badge.classList.add("hidden"); } } function updateMuteVisibility() { const mute = chatClient.getMuteMode(); if (!bubble || !panel) return; if (mute === "hide") { bubble.classList.add("hidden"); panel.classList.add("hidden"); panelOpen = false; } else { bubble.classList.remove("hidden"); } if (mute === "mute_collapse" && panelOpen) { // auto collapse handled on new messages } } function togglePanel() { if (!panel) return; panelOpen = !panelOpen; if (panelOpen) { panel.classList.remove("hidden"); panel.style.opacity = "0"; panel.style.transform = "translateY(10px) scale(0.98)"; requestAnimationFrame(() => { panel.style.transition = "opacity 0.15s ease, transform 0.15s ease"; panel.style.opacity = "1"; panel.style.transform = "translateY(0) scale(1)"; }); chatClient.resetUnread(); updateBubbleBadge(); scrollToBottom(); if (inputArea) inputArea.focus(); } else { panel.style.transition = "opacity 0.1s ease, transform 0.1s ease"; panel.style.opacity = "0"; panel.style.transform = "translateY(10px) scale(0.98)"; setTimeout(() => { if (!panelOpen) panel.classList.add("hidden"); }, 100); } } async function loadMoreHistory() { if (loadingMore || !hasMore || !messageList) return; loadingMore = true; const result = await chatClient.fetchHistory(oldestTimestamp, 50); const msgs = result.messages || []; if (msgs.length === 0) { hasMore = false; loadingMore = false; return; } const prevHeight = messageList.scrollHeight; const prevTop = messageList.scrollTop; const frag = document.createDocumentFragment(); for (const msg of msgs) { frag.appendChild(createMessageEl(msg)); if (!oldestTimestamp || msg.createdAt < oldestTimestamp) { oldestTimestamp = msg.createdAt; } } messageList.insertBefore(frag, messageList.firstChild); const newHeight = messageList.scrollHeight; messageList.scrollTop = prevTop + (newHeight - prevHeight); if (msgs.length < 50) hasMore = false; loadingMore = false; } function appendMessage(msg) { if (!messageList) return; const wasAtBottom = messageList.scrollHeight - messageList.scrollTop - messageList.clientHeight < 40; messageList.appendChild(createMessageEl(msg)); if (!oldestTimestamp || msg.createdAt < oldestTimestamp) { oldestTimestamp = msg.createdAt; } if (wasAtBottom) scrollToBottom(); } function handleSend() { if (!inputArea) return; const text = inputArea.value; if (chatClient.sendMessage(text)) { inputArea.value = ""; inputArea.style.height = "auto"; } } function createWidget() { bubble = document.createElement("div"); bubble.id = "chat-bubble"; bubble.className = "fixed bottom-5 right-5 z-[9998] cursor-pointer select-none"; bubble.innerHTML = `