Open chat on the newest message and keep it pinned
This commit is contained in:
+54
-2
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user