Added code.
This commit is contained in:
104
client/public/render.js
Normal file
104
client/public/render.js
Normal file
@@ -0,0 +1,104 @@
|
||||
// Render module: handles chat UI rendering of messages and notices
|
||||
import { getThreads, getRootIds, getReplyTo, getFocused } from "./data.js";
|
||||
|
||||
/**
|
||||
* Render the chat history and input container into the provided chat element.
|
||||
* @param {HTMLElement} chatEl - The container element for chat entries.
|
||||
* @param {HTMLElement} inputContainer - The message input area to append at bottom.
|
||||
*/
|
||||
export function renderChat(chatEl, inputContainer) {
|
||||
// Clear existing content.
|
||||
chatEl.innerHTML = "";
|
||||
|
||||
const threads = getThreads();
|
||||
const rootIds = getRootIds();
|
||||
const replyTo = getReplyTo();
|
||||
let replyDepth = 0;
|
||||
if (replyTo !== null) {
|
||||
let cur = threads.get(replyTo);
|
||||
while (cur && cur.parent != null) {
|
||||
replyDepth++;
|
||||
cur = threads.get(cur.parent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively render a thread node and its children.
|
||||
* @param {number} id - The message or notice ID.
|
||||
* @param {number} depth - Nesting level (for indentation).
|
||||
*/
|
||||
function renderNode(id, depth = 0) {
|
||||
const msg = threads.get(id);
|
||||
if (!msg) return;
|
||||
|
||||
// Create wrapper div.
|
||||
const div = document.createElement("div");
|
||||
div.dataset.id = id;
|
||||
div.style.marginLeft = `${depth}em`;
|
||||
|
||||
// Format timestamp.
|
||||
const date = new Date(msg.ts * 1000);
|
||||
let h = date.getHours();
|
||||
const ampm = h >= 12 ? "PM" : "AM";
|
||||
h = h % 12 || 12;
|
||||
let m = date.getMinutes();
|
||||
if (m < 10) m = "0" + m;
|
||||
const fullTimestamp =
|
||||
`${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()} ` +
|
||||
`${h}:${m} ${ampm}`;
|
||||
const shortTimestamp = `${h}:${m} ${ampm}`;
|
||||
const tsSpan = `<span class="ts" title="${fullTimestamp}">${shortTimestamp}</span>`;
|
||||
|
||||
// Distinguish notice vs normal message.
|
||||
if (msg.username) {
|
||||
div.classList.add("msg");
|
||||
if (getFocused() === id) div.classList.add("focused");
|
||||
div.innerHTML = `${tsSpan} <span class="name">${new Option(msg.username).innerHTML}:</span> <span class="msg_content">${new Option(msg.content).innerHTML}</span>`;
|
||||
} else {
|
||||
div.classList.add("notice");
|
||||
div.innerHTML = `${tsSpan} ${msg.content}`;
|
||||
}
|
||||
|
||||
// Append to chat and render children.
|
||||
chatEl.appendChild(div);
|
||||
msg.children.forEach((childId) => renderNode(childId, depth + 1));
|
||||
}
|
||||
|
||||
// Render all root-level nodes in order.
|
||||
rootIds.forEach((id) => renderNode(id));
|
||||
|
||||
// Append input container at appropriate thread level.
|
||||
{
|
||||
const indent = replyTo !== null ? replyDepth + 1 : 0;
|
||||
inputContainer.style.paddingLeft = `${indent}em`;
|
||||
|
||||
// Place input under the last descendant if replying, else at bottom.
|
||||
let refDiv = null;
|
||||
if (replyTo !== null) {
|
||||
function lastDesc(id) {
|
||||
const node = threads.get(id);
|
||||
return !node || node.children.length === 0
|
||||
? id
|
||||
: lastDesc(node.children[node.children.length - 1]);
|
||||
}
|
||||
const lastId = lastDesc(replyTo);
|
||||
refDiv = chatEl.querySelector(`div[data-id="${lastId}"]`);
|
||||
}
|
||||
if (refDiv) {
|
||||
refDiv.insertAdjacentElement("afterend", inputContainer);
|
||||
} else {
|
||||
chatEl.appendChild(inputContainer);
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll to bottom.
|
||||
chatEl.scrollTop = chatEl.scrollHeight;
|
||||
const input = inputContainer.querySelector("input");
|
||||
if (input) {
|
||||
input.focus();
|
||||
// Ensure inputContainer margin resets if at root.
|
||||
if (replyTo === null) {
|
||||
inputContainer.style.marginLeft = "0";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user