149 lines
4.2 KiB
JavaScript
149 lines
4.2 KiB
JavaScript
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(
|
|
`<span class="err">Name may be at most ${MAX_NAME_LEN} characters.</span>`,
|
|
);
|
|
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" });
|
|
}
|
|
});
|
|
});
|