Open chat on the newest message and keep it pinned

This commit is contained in:
Fisto
2026-09-22 11:37:54 +02:00
parent 7dd45c483a
commit 29df3c6870
3 changed files with 64 additions and 2 deletions
+54 -2
View File
@@ -50,6 +50,8 @@ const state = {
consoleOpen: false,
consoleTab: "events",
mobileTab: localStorage.getItem("veskslop-mobile-tab") ?? "chat",
renderedChannelId: null,
messagesPinned: true,
voiceReloadAt: 0
};
@@ -1099,12 +1101,42 @@ function renderCenterHeader() {
$("#channel-context").textContent = context;
}
/* Message scroll: a channel opens on the newest message. The view stays pinned
while the user is at the bottom, and never gets dragged by content that loads
late above the viewport. */
function messagesNearBottom(slack = 80) {
const scroll = $("#message-scroll");
if (!scroll || !scroll.scrollHeight) return true;
return scroll.scrollHeight - scroll.scrollTop - scroll.clientHeight <= slack;
}
function pinMessagesToBottom() {
const scroll = $("#message-scroll");
if (!scroll) return;
const apply = () => {
scroll.scrollTop = scroll.scrollHeight;
};
apply();
requestAnimationFrame(apply);
// Avatars, attachments, and embeds can change the height after the first paint.
setTimeout(apply, 120);
setTimeout(apply, 400);
}
function renderMessages() {
const root = $("#message-list");
const channelChanged = state.renderedChannelId !== state.channelId;
const pinned = channelChanged || messagesNearBottom();
state.renderedChannelId = state.channelId;
state.messagesPinned = pinned;
root.replaceChildren();
if (!state.channelId) {
root.append(el("div", { class: "vk-empty", text: "Pick a channel on the left." }));
pinMessagesToBottom();
return;
}
@@ -1115,6 +1147,7 @@ function renderMessages() {
text: "No cached messages. The client only holds channels it has loaded."
})
);
pinMessagesToBottom();
return;
}
@@ -1176,8 +1209,7 @@ function renderMessages() {
root.append(el("div", { class: "vk-message" }, avatar(message.author ?? author, { agent: isAgent, size: "lg" }), body));
}
const scroll = $("#message-scroll");
scroll.scrollTop = scroll.scrollHeight;
if (pinned) pinMessagesToBottom();
}
function ttsToggleButton() {
@@ -1529,6 +1561,11 @@ function setMobileTab(tab) {
for (const button of $$(".vk-tab")) button.classList.toggle("active", button.dataset.mobileTab === tab);
if (isMobileLayout()) $(".vk-app").dataset.mobileTab = tab;
// The chat panel was hidden during the last render: put the view back on the
// newest message.
if (tab === "chat" && state.messagesPinned) pinMessagesToBottom();
renderRightVoice();
updateMobileBadge();
}
@@ -2871,6 +2908,21 @@ function init() {
}
});
$("#channel-refresh").addEventListener("click", () => void loadMessages({ refresh: true }));
$("#message-scroll").addEventListener(
"scroll",
() => {
state.messagesPinned = messagesNearBottom();
},
{ passive: true }
);
// Re-pin while the user is at the bottom and late images grow the list.
$("#message-list").addEventListener(
"load",
() => {
if (state.messagesPinned) pinMessagesToBottom();
},
true
);
$("#guilds-show-all").addEventListener("click", () => {
state.visibleGuilds = null;