Chat
This commit is contained in:
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user