This commit is contained in:
2026-10-21 12:11:21 -04:00
parent ec36013313
commit b340ec3814
6 changed files with 275 additions and 0 deletions
+66
View File
@@ -10,11 +10,14 @@ let statsLoaded = false;
let subscribed = false;
let currentShareText = "";
let selectedSection = null;
let chatSignature = null;
let chatLoading = false;
function renderSections() {
const available = {
"account-section": true,
"leaderboard-section": true,
"chat-section": true,
"stats-section": statsLoaded,
rules: true,
"share-controls": !!currentShareText,
@@ -32,6 +35,8 @@ function renderSections() {
panel.hidden = panel.id !== selectedSection;
});
$("mailing-list").hidden = !token || subscribed;
$("chat-form").hidden = !token;
$("chat-login").hidden = !!token;
$("section-hint").hidden = selectedSection !== null;
}
@@ -42,6 +47,7 @@ $("section-nav").addEventListener("click", (event) => {
renderSections();
if (selectedSection === "account-section") loadMe();
if (selectedSection === "leaderboard-section") loadLeaderboard();
if (selectedSection === "chat-section") loadChat();
});
async function api(method, path, body) {
@@ -185,6 +191,7 @@ async function refreshState() {
if (game.ended && !statsLoaded) loadStats();
updateButton();
await loadLeaderboard();
if (selectedSection === "chat-section") await loadChat();
}
function updateButton() {
@@ -254,6 +261,65 @@ async function loadLeaderboard() {
}
}
async function loadChat() {
if (chatLoading) return;
chatLoading = true;
try {
const { messages } = await api("GET", "/api/chat");
const signature = messages.map((message) => message.id).join(",");
const list = $("chat-messages");
if (signature !== chatSignature) {
const atBottom = list.scrollHeight - list.scrollTop - list.clientHeight < 40;
const firstLoad = chatSignature === null;
const previousScroll = list.scrollTop;
list.replaceChildren(...messages.map((message) => {
const item = el("li");
const avatar = el("span", Array.from(message.name.trim())[0]?.toUpperCase() || "?", "chat-avatar");
avatar.setAttribute("aria-hidden", "true");
const content = el("div", undefined, "chat-message-content");
const heading = el("div", undefined, "chat-heading");
const date = new Date(message.at);
const timestamp = el("time", date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }));
timestamp.dateTime = new Date(message.at).toISOString();
timestamp.title = date.toLocaleString();
heading.append(el("strong", message.name), timestamp);
// Names and messages are untrusted text, never HTML.
content.append(heading, el("p", message.message));
item.append(avatar, content);
return item;
}));
chatSignature = signature;
list.scrollTop = firstLoad || atBottom ? list.scrollHeight : previousScroll;
}
$("chat-status").textContent = messages.length ? "" : "No messages yet. Start the conversation!";
} catch {
$("chat-status").textContent = "Messages could not be refreshed. Retrying automatically...";
} finally {
chatLoading = false;
}
}
$("chat-form").addEventListener("submit", async (event) => {
event.preventDefault();
const input = $("chat-input");
$("chat-send").disabled = true;
$("chat-error").textContent = "";
try {
await api("POST", "/api/chat", { message: input.value });
input.value = "";
await loadChat();
} catch (error) {
$("chat-error").textContent = error.message;
if (error.status === 401) {
token = null;
localStorage.removeItem(TOKEN_KEY);
updateButton();
}
} finally {
$("chat-send").disabled = false;
}
});
async function loadStats() {
try {
const s = await api("GET", "/api/stats");