Merge pull request 'Fixed scrolling behavior, reply, cross channel messages and gg navigation' (#66) from BordedDev/snek:bugfix/multiple-issues-with-new-chat into main
Reviewed-on: #66
This commit is contained in:
commit
17bb88050a
@ -368,7 +368,7 @@ input[type="text"], .chat-input textarea {
|
||||
}
|
||||
}
|
||||
|
||||
.message.switch-user + .message, .message.long-time + .message, .message:first-child {
|
||||
.message.switch-user + .message, .message.long-time + .message, .message-list-bottom + .message{
|
||||
.time {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
|
@ -9,8 +9,53 @@ import {app} from "./app.js";
|
||||
|
||||
const LONG_TIME = 1000 * 60 * 20
|
||||
|
||||
export class ReplyEvent extends Event {
|
||||
constructor(messageTextTarget) {
|
||||
super('reply', { bubbles: true, composed: true });
|
||||
this.messageTextTarget = messageTextTarget;
|
||||
|
||||
const newMessage = messageTextTarget.cloneNode(true);
|
||||
newMessage.style.maxHeight = "0"
|
||||
messageTextTarget.parentElement.insertBefore(newMessage, messageTextTarget);
|
||||
|
||||
newMessage.querySelectorAll('.embed-url-link').forEach(link => {
|
||||
link.remove()
|
||||
})
|
||||
|
||||
newMessage.querySelectorAll('picture').forEach(picture => {
|
||||
const img = picture.querySelector('img');
|
||||
if (img) {
|
||||
picture.replaceWith(img);
|
||||
}
|
||||
})
|
||||
|
||||
newMessage.querySelectorAll('img').forEach(img => {
|
||||
const src = img.src || img.currentSrc;
|
||||
img.replaceWith(document.createTextNode(src));
|
||||
})
|
||||
|
||||
newMessage.querySelectorAll('iframe').forEach(iframe => {
|
||||
const src = iframe.src || iframe.currentSrc;
|
||||
iframe.replaceWith(document.createTextNode(src));
|
||||
})
|
||||
|
||||
newMessage.querySelectorAll('a').forEach(a => {
|
||||
const href = a.getAttribute('href');
|
||||
const text = a.innerText || a.textContent;
|
||||
if (text === href || text === '') {
|
||||
a.replaceWith(document.createTextNode(href));
|
||||
} else {
|
||||
a.replaceWith(document.createTextNode(`[${text}](${href})`));
|
||||
}
|
||||
})
|
||||
|
||||
this.replyText = newMessage.innerText.replaceAll("\n\n", "\n").trim();
|
||||
newMessage.remove()
|
||||
}
|
||||
}
|
||||
|
||||
class MessageElement extends HTMLElement {
|
||||
static observedAttributes = ['data-uid', 'data-color', 'data-channel_uid', 'data-user_nick', 'data-created_at', 'data-user_uid'];
|
||||
// static observedAttributes = ['data-uid', 'data-color', 'data-channel_uid', 'data-user_nick', 'data-created_at', 'data-user_uid'];
|
||||
|
||||
isVisible() {
|
||||
if (!this) return false;
|
||||
@ -51,6 +96,12 @@ class MessageElement extends HTMLElement {
|
||||
}
|
||||
|
||||
this.timeDiv = this.querySelector('.time span');
|
||||
this.replyDiv = this.querySelector('.time a');
|
||||
|
||||
this.replyDiv.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.dispatchEvent(new ReplyEvent(this.messageDiv));
|
||||
})
|
||||
}
|
||||
|
||||
if (!this.siblingGenerated && this.nextElementSibling) {
|
||||
@ -99,7 +150,9 @@ class MessageList extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
app.ws.addEventListener("update_message_text", (data) => {
|
||||
this.upsertMessage(data);
|
||||
if (this.messageMap.has(data.uid)) {
|
||||
this.upsertMessage(data);
|
||||
}
|
||||
});
|
||||
app.ws.addEventListener("set_typing", (data) => {
|
||||
this.triggerGlow(data.user_uid,data.color);
|
||||
@ -108,29 +161,33 @@ class MessageList extends HTMLElement {
|
||||
this.messageMap = new Map();
|
||||
this.visibleSet = new Set();
|
||||
this._observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
this.visibleSet.add(entry.target);
|
||||
const messageElement = entry.target;
|
||||
if (messageElement instanceof MessageElement) {
|
||||
messageElement.updateUI();
|
||||
}
|
||||
} else {
|
||||
this.visibleSet.delete(entry.target);
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
this.visibleSet.add(entry.target);
|
||||
const messageElement = entry.target;
|
||||
if (messageElement instanceof MessageElement) {
|
||||
messageElement.updateUI();
|
||||
}
|
||||
});
|
||||
console.log(this.visibleSet);
|
||||
} else {
|
||||
this.visibleSet.delete(entry.target);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
root: this,
|
||||
threshold: 0.1
|
||||
root: this,
|
||||
threshold: 0,
|
||||
})
|
||||
|
||||
for(const c of this.children) {
|
||||
this._observer.observe(c);
|
||||
if (c instanceof MessageElement) {
|
||||
this.messageMap.set(c.dataset.uid, c);
|
||||
}
|
||||
if (c instanceof MessageElement) {
|
||||
this.messageMap.set(c.dataset.uid, c);
|
||||
}
|
||||
}
|
||||
|
||||
this.endOfMessages = document.createElement('div');
|
||||
this.endOfMessages.classList.add('message-list-bottom');
|
||||
this.prepend(this.endOfMessages);
|
||||
|
||||
this.scrollToBottom(true);
|
||||
}
|
||||
|
||||
@ -174,7 +231,6 @@ class MessageList extends HTMLElement {
|
||||
};
|
||||
document.addEventListener('keydown', escListener);
|
||||
})
|
||||
|
||||
}
|
||||
isElementVisible(element) {
|
||||
if (!element) return false;
|
||||
@ -187,13 +243,13 @@ class MessageList extends HTMLElement {
|
||||
);
|
||||
}
|
||||
isScrolledToBottom() {
|
||||
return this.isElementVisible(this.firstElementChild);
|
||||
return this.isElementVisible(this.endOfMessages);
|
||||
}
|
||||
scrollToBottom(force = false, behavior= 'smooth') {
|
||||
if (force || this.isScrolledToBottom()) {
|
||||
this.firstElementChild.scrollIntoView({ behavior, block: 'start' });
|
||||
scrollToBottom(force = false, behavior= 'instant') {
|
||||
if (force || !this.isScrolledToBottom()) {
|
||||
this.firstElementChild.scrollIntoView({ behavior, block: 'end' });
|
||||
setTimeout(() => {
|
||||
this.firstElementChild.scrollIntoView({ behavior, block: 'start' });
|
||||
this.firstElementChild.scrollIntoView({ behavior, block: 'end' });
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
@ -227,9 +283,8 @@ class MessageList extends HTMLElement {
|
||||
|
||||
upsertMessage(data) {
|
||||
let message = this.messageMap.get(data.uid);
|
||||
const newMessage = !!message;
|
||||
if (message) {
|
||||
message.parentElement.removeChild(message);
|
||||
message.parentElement?.removeChild(message);
|
||||
}
|
||||
|
||||
if (!data.message) return
|
||||
@ -239,16 +294,16 @@ class MessageList extends HTMLElement {
|
||||
wrapper.innerHTML = data.html;
|
||||
|
||||
if (message) {
|
||||
message.updateMessage(...wrapper.firstElementChild._originalChildren);
|
||||
message.updateMessage(...(wrapper.firstElementChild._originalChildren || wrapper.firstElementChild.children));
|
||||
} else {
|
||||
message = wrapper.firstElementChild;
|
||||
this.messageMap.set(data.uid, message);
|
||||
this._observer.observe(message);
|
||||
message = wrapper.firstElementChild;
|
||||
this.messageMap.set(data.uid, message);
|
||||
this._observer.observe(message);
|
||||
}
|
||||
|
||||
const scrolledToBottom = this.isScrolledToBottom();
|
||||
this.prepend(message);
|
||||
if (scrolledToBottom) this.scrollToBottom(true, !newMessage ? 'smooth' : 'auto');
|
||||
if (scrolledToBottom) this.scrollToBottom(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,12 +72,13 @@ function throttle(fn, wait) {
|
||||
// --- Scroll: load extra messages, throttled ---
|
||||
let isLoadingExtra = false;
|
||||
async function loadExtra() {
|
||||
const firstMessage = messagesContainer.children[messagesContainer.children.length - 1];
|
||||
const firstMessage = messagesContainer.lastElementChild;
|
||||
if (isLoadingExtra || !isScrolledPastHalf() || !firstMessage) return;
|
||||
isLoadingExtra = true;
|
||||
const messages = await app.rpc.getMessages(channelUid, 0, firstMessage.dataset.created_at);
|
||||
if (messages.length) {
|
||||
const frag = document.createDocumentFragment();
|
||||
messages.reverse();
|
||||
messages.forEach(msg => {
|
||||
const temp = document.createElement("div");
|
||||
temp.innerHTML = msg.html;
|
||||
@ -138,10 +139,16 @@ chatInputField.textarea.focus();
|
||||
|
||||
// --- Reply helper ---
|
||||
function replyMessage(message) {
|
||||
chatInputField.value = "```markdown\n> " + (message || '').split("\n").join("\n> ") + "\n```\n";
|
||||
chatInputField.value = "```markdown\n> " + (message || '').trim().split("\n").join("\n> ") + "\n```\n";
|
||||
chatInputField.textarea.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
chatInputField.focus();
|
||||
}
|
||||
|
||||
messagesContainer.addEventListener("reply", (e) => {
|
||||
const messageText = e.replyText || e.messageTextTarget.textContent.trim();
|
||||
replyMessage(messageText);
|
||||
})
|
||||
|
||||
// --- Mention helpers ---
|
||||
function extractMentions(message) {
|
||||
return [...new Set(message.match(/@\w+/g) || [])];
|
||||
@ -215,7 +222,7 @@ document.addEventListener('keydown', function(event) {
|
||||
keyTimeout = setTimeout(() => { gPressCount = 0; }, 300);
|
||||
if (gPressCount === 2) {
|
||||
gPressCount = 0;
|
||||
messagesContainer.querySelector(".message:first-child")?.scrollIntoView({ block: "end", inline: "nearest" });
|
||||
messagesContainer.lastElementChild?.scrollIntoView({ block: "end", inline: "nearest" });
|
||||
loadExtra();
|
||||
}
|
||||
}
|
||||
@ -254,7 +261,7 @@ function updateLayout(doScrollDown) {
|
||||
function isScrolledPastHalf() {
|
||||
let scrollTop = messagesContainer.scrollTop;
|
||||
let scrollableHeight = messagesContainer.scrollHeight - messagesContainer.clientHeight;
|
||||
return scrollTop < scrollableHeight / 2;
|
||||
return Math.abs(scrollTop) > scrollableHeight / 2;
|
||||
}
|
||||
|
||||
// --- Initial layout update ---
|
||||
|
Loading…
Reference in New Issue
Block a user