function toggleSettings() { const panel = document.getElementById("settings-panel"); panel.style.display = panel.style.display === "none" ? "block" : "none"; if (panel.style.display === "block") { const username = document.getElementById("username"); username.focus(); username.selectionStart = username.selectionEnd = username.value .length; } } function toggleUsers() { const panel = document.getElementById("users-panel"); panel.style.display = panel.style.display === "none" ? "block" : "none"; } function toggleTheme() { const currentTheme = document.documentElement.getAttribute( "data-theme", ); const newTheme = currentTheme === "dark" ? "light" : "dark"; document.documentElement.setAttribute("data-theme", newTheme); localStorage.setItem("theme", newTheme); } document.addEventListener("click", function (event) { const settingsPanel = document.getElementById("settings-panel"); const settingsButton = document.querySelector(".settings-button"); const usersPanel = document.getElementById("users-panel"); const usersButton = document.querySelector(".users-button"); if ( !settingsPanel.contains(event.target) && !settingsButton.contains(event.target) ) { settingsPanel.style.display = "none"; } if ( !usersPanel.contains(event.target) && !usersButton.contains(event.target) ) { usersPanel.style.display = "none"; } }); document.addEventListener("keypress", function (event) { if (event.key === "Enter" && !event.shiftKey) { const settingsPanel = document.getElementById("settings-panel"); const inputPanel = document.getElementById("message"); if (settingsPanel.contains(event.target)) { setUsername(); } if (inputPanel.contains(event.target)) { event.preventDefault(); sendMessage(); } } }); document.addEventListener("input", function (_) { const msg = document.getElementById("message"); msg.style.height = "auto"; msg.style.height = (msg.scrollHeight) + "px"; }); document.addEventListener("blur", function (_) { const msg = document.getElementById("message"); msg.style.height = "auto"; }, true); async function loadUsers() { try { const response = await fetch("/users"); const data = await response.json(); const usersList = document.getElementById("users-list"); usersList.innerHTML = ""; data.users.sort().forEach((user) => { const userDiv = document.createElement("div"); userDiv.className = "user-item"; userDiv.textContent = user; usersList.appendChild(userDiv); }); } catch (error) { console.error("Error loading users:", error); } } let lastMessageCount = 0; async function loadMessages() { try { let messagesDiv = document.getElementById("messages"); const response = await fetch("/messages"); const text = await response.text(); const tempDiv = document.createElement("div"); tempDiv.innerHTML = text; // getting an error right here that messagesDiv is null // but only on the first load, so maybe we can just hold off? while (messagesDiv === null) { await new Promise((resolve) => setTimeout(resolve, 100) ); messagesDiv = document.getElementById("messages"); } if (messagesDiv.scrollTop != bottom) { // show a button to scroll to the bottom const scrollToBottomButton = document.getElementById( "scroll", ); scrollToBottomButton.style.display = "block"; scrollToBottomButton.onclick = scrollToBottom; } else { // hide the button const scrollToBottomButton = document.getElementById( "scroll", ); scrollToBottomButton.style.display = "none"; } const messages = tempDiv.getElementsByTagName("p"); const newMessageCount = messages.length; if ( newMessageCount > lastMessageCount || lastMessageCount === 0 ) { messagesDiv.innerHTML = ""; Array.from(messages).forEach((msg) => { const messageDiv = document.createElement( "div", ); messageDiv.className = "message"; const [username, content] = msg.innerHTML.split( "
", ); const linkedContent = content.replace( /(?![^<]*>)(https?:\/\/[^\s<]+)/g, function (url) { console.log( "Processing URL:", url, ); // Debug log const videoId = getYouTubeID( url, ); if (videoId) { return `${url}
`; } else if (isImageUrl(url)) { console.log( "Attempting to embed image:", url, ); return `${url}
Embedded image
`; } return `${url}`; }, ); messageDiv.innerHTML = '
' + username + "
" + '
' + linkedContent + "
"; messagesDiv.appendChild(messageDiv); }); scrollToBottom(); lastMessageCount = newMessageCount; } } catch (error) { console.error("Error loading messages:", error); } } function isImageUrl(url) { return url.match(/\.(jpeg|jpg|gif|png|webp|bmp)($|\?)/i) != null; } function getYouTubeID(url) { // First check if it's a Shorts URL if (url.includes("/shorts/")) { const shortsMatch = url.match(/\/shorts\/([^/?]+)/); return shortsMatch ? shortsMatch[1] : false; } // Otherwise check regular YouTube URLs const regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/; const match = url.match(regExp); return (match && match[7].length == 11) ? match[7] : false; } var bottom = 0; function scrollToBottom() { const messagesDiv = document.getElementById("messages"); messagesDiv.scrollTop = messagesDiv.scrollHeight; bottom = messagesDiv.scrollTop; } async function checkUsername() { try { const response = await fetch("/username/status"); const data = await response.json(); if (!data.hasUsername) { document.getElementById("settings-panel").style .display = "block"; const username = document.getElementById("username"); username.focus(); username.selectionStart = username.selectionEnd = username.value.length; } } catch (error) { console.error("Error checking username status:", error); } } async function updateCurrentUser() { try { const response = await fetch("/username/status"); const data = await response.json(); const userDiv = document.getElementById("current-user"); if (data.hasUsername) { userDiv.textContent = data.username; } else { userDiv.textContent = ""; } } catch (error) { console.error("Error getting username:", error); } } async function setUsername() { const username = document.getElementById("username").value; if (!username) { showUsernameStatus("Please enter a username", "red"); return; } try { const response = await fetch("/username", { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ username: username }), }); const data = await response.json(); if (response.ok) { showUsernameStatus( "Username set successfully!", "green", ); updateCurrentUser(); setTimeout(() => { document.getElementById("settings-panel").style .display = "none"; }, 500); location.reload(); } else { showUsernameStatus( data.error || "Failed to set username", "red", ); } } catch (error) { showUsernameStatus("Error connecting to server", "red"); } } async function sendMessage() { const messageInput = document.getElementById("message"); const message = messageInput.value; if (!message) { return; } try { const response = await fetch("/messages", { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ message: message }), }); const data = await response.json(); if (response.ok) { messageInput.value = ""; messageInput.style.height = "auto"; loadMessages(); } else { showStatus( data.error || "Failed to send message", "red", ); } } catch (error) { showStatus("Error connecting to server", "red"); } } function showStatus(message, color) { const status = document.getElementById("status"); status.textContent = message; status.style.color = color; setTimeout(() => { status.textContent = ""; }, 3000); } function showUsernameStatus(message, color) { const status = document.getElementById("username-status"); status.textContent = message; status.style.color = color; setTimeout(() => { status.textContent = ""; }, 3000); } async function pingCheck() { try { await fetch("/ping", { method: "POST" }); } catch (error) { console.error("Ping failed:", error); } } async function timeZoneCheck() { try { const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; const response = await fetch("/timezone", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ timezone: timeZone }), }); const data = await response.json(); if (!response.ok) { console.error("Failed to set timezone:", data.error); } } catch (error) { console.error("Error checking timezone:", error); } } function initializeTheme() { const savedTheme = localStorage.getItem("theme"); if (savedTheme) { document.documentElement.setAttribute("data-theme", savedTheme); } else { // Check system preference if ( window.matchMedia && window.matchMedia("(prefers-color-scheme: light)") .matches ) { document.documentElement.setAttribute( "data-theme", "light", ); } else { document.documentElement.setAttribute( "data-theme", "dark", ); } } } async function initialize() { usersPanel = document.getElementById("users-panel"); if (usersPanel) { usersPanel.style.display = "none"; } settingsPanel = document.getElementById("settings-panel"); if (settingsPanel) { settingsPanel.style.display = "none"; } initializeTheme(); checkUsername(); updateCurrentUser(); timeZoneCheck(); setInterval(loadMessages, 1000); setInterval(loadUsers, 1000); setInterval(pingCheck, 3000); await loadMessages(); scrollToBottom(); } initialize();