function toggleSettings() { const panel = document.getElementById("settings-panel"); panel.style.display = panel.style.display === "block" ? "none" : "block"; 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 === "block" ? "none" : "block"; } 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); } async function deleteMessage(messageId) { if (confirm("Delete this message?")) { try { const response = await fetch("/messages", { method: "DELETE", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ messageId: messageId }), }); if (response.ok) { // Refresh messages console.log("Message deleted successfully"); location.reload(); } else { console.error("Failed to delete message"); } } catch (error) { console.error("Error deleting message:", error); } } } 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 [ messageId, username, timestamp, 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 `
`; } else if (isImageUrl(url)) { console.log( "Attempting to embed image:", url, ); return `
Embedded image
`; } return `${url}`; }, ); let deleteHtml = ""; const usernameDiv = document.createElement( "div", ); usernameDiv.innerHTML = username; compareUsername = usernameDiv.textContent; if ( compareUsername === document.getElementById( "current-user", ).textContent ) { deleteHtml = ``; } messageDiv.innerHTML = `
${username} ${timestamp} ${deleteHtml}
${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"); } } let lastMessage = ""; async function sendMessage() { const messageInput = document.getElementById("message"); const message = messageInput.value; if (!message) { return; } try { lastMessage = message; 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", ); } } } document.addEventListener("keyup", function (event) { const inputPanel = document.getElementById("message"); if (inputPanel.contains(event.target) && event.key === "ArrowUp") { if (inputPanel.value === "") { inputPanel.value = lastMessage; } } }); 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();