import { setReplyTo, getThreads, addNotice, updateUser, getGroupedUsers, getMyId, getUsers, getFocused, setFocused, clearFocused, } from "./data.js"; import { renderChat } from "./render.js"; import { initWebSocket } from "./wsModule.js"; document.addEventListener("DOMContentLoaded", () => { const chat = document.getElementById("chat"); const inputContainer = document.getElementById("inputContainer"); const rootButton = document.getElementById("rootButton"); const sendButton = document.getElementById("sendButton"); const input = document.getElementById("messageInput"); const usersList = document.getElementById("userList"); const nameInput = document.getElementById("nameInput"); const nameButton = document.getElementById("nameButton"); chat.style.display = ""; inputContainer.style.display = "flex"; const defaultName = "Anon"; const MAX_NAME_LEN = 31; const { socket, sendMessage, sendName } = initWebSocket( defaultName, chat, inputContainer, usersList, ); // Renames. nameButton.addEventListener("click", () => { const newName = nameInput.value.trim() || defaultName; if (newName.length > MAX_NAME_LEN) { addNotice( `Name may be at most ${MAX_NAME_LEN} characters.`, ); renderChat(chat, inputContainer); nameInput.focus(); return; } sendName(newName); updateUser(getMyId(), newName); const groups = getGroupedUsers(); usersList.innerHTML = "Online: " + groups.join(", "); nameInput.value = ""; nameInput.placeholder = `Name: ${newName}`; input.focus(); }); // Enter to set name. nameInput.addEventListener("keydown", (e) => { if (e.key === "Enter") nameButton.click(); }); // Button to send. sendButton.addEventListener("click", () => { const text = input.value.trim(); if (!text) return; sendMessage(text); input.value = ""; input.focus(); }); // Enter to send and escape to go to root. input.addEventListener("keydown", (e) => { if (e.key === "Enter") { sendButton.click(); } else if (e.key === "Escape") { setReplyTo(null); input.placeholder = ""; renderChat(chat, inputContainer); input.focus(); } }); // Click message to reply. chat.addEventListener("click", (e) => { const msgDiv = e.target.closest(".msg"); if (!msgDiv) return; const id = Number(msgDiv.dataset.id); if (!isFinite(id)) return; const threads = getThreads(); const author = threads.get(id)?.username; setReplyTo(id); setFocused(id); input.placeholder = author ? `Replying to @${author}` : ""; renderChat(chat, inputContainer); input.focus(); }); // Go back to root. rootButton.addEventListener("click", () => { setReplyTo(null); clearFocused(); input.placeholder = ""; renderChat(chat, inputContainer); input.focus(); }); // Global Escape handler: clear reply context, focus input, and clear focused message. document.addEventListener("keydown", (e) => { if (e.key === "Escape") { setReplyTo(null); clearFocused(); input.placeholder = ""; renderChat(chat, inputContainer); input.focus(); } }); // Arrow key navigation for focused messages (also sets reply target). document.addEventListener("keydown", (e) => { if (e.key === "ArrowDown" || e.key === "ArrowUp") { const msgs = Array.from(chat.querySelectorAll("div.msg")); if (!msgs.length) return; let idx = msgs.findIndex( (div) => Number(div.dataset.id) === getFocused(), ); if (idx === -1) { idx = e.key === "ArrowDown" ? -1 : msgs.length; } idx = e.key === "ArrowDown" ? Math.min(msgs.length - 1, idx + 1) : Math.max(0, idx - 1); const newId = Number(msgs[idx].dataset.id); setFocused(newId); setReplyTo(newId); // Move input box under focused message. const threads = getThreads(); const author = threads.get(newId)?.username; input.placeholder = author ? `Replying to @${author}` : ""; renderChat(chat, inputContainer); const newDiv = chat.querySelector(`div[data-id="${newId}"]`); if (newDiv) newDiv.scrollIntoView({ block: "nearest" }); } }); });