|
|
|
@@ -11,8 +11,46 @@ const LONG_TIME = 1000 * 60 * 20
|
|
|
|
|
|
|
|
|
|
export class ReplyEvent extends Event {
|
|
|
|
|
constructor(messageTextTarget) {
|
|
|
|
|
super('reply', { bubbles: true, composed: true });
|
|
|
|
|
this.messageTextTarget = 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()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -123,28 +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();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -188,7 +231,6 @@ class MessageList extends HTMLElement {
|
|
|
|
|
};
|
|
|
|
|
document.addEventListener('keydown', escListener);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
isElementVisible(element) {
|
|
|
|
|
if (!element) return false;
|
|
|
|
@@ -201,13 +243,13 @@ class MessageList extends HTMLElement {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
isScrolledToBottom() {
|
|
|
|
|
return this.isElementVisible(this.firstElementChild);
|
|
|
|
|
return this.isElementVisible(this.endOfMessages);
|
|
|
|
|
}
|
|
|
|
|
scrollToBottom(force = false, behavior= 'instant') {
|
|
|
|
|
if (force || !this.isScrolledToBottom()) {
|
|
|
|
|
this.firstElementChild.scrollIntoView({ behavior, block: 'start' });
|
|
|
|
|
this.firstElementChild.scrollIntoView({ behavior, block: 'end' });
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
this.firstElementChild.scrollIntoView({ behavior, block: 'start' });
|
|
|
|
|
this.firstElementChild.scrollIntoView({ behavior, block: 'end' });
|
|
|
|
|
}, 200);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -241,7 +283,6 @@ class MessageList extends HTMLElement {
|
|
|
|
|
|
|
|
|
|
upsertMessage(data) {
|
|
|
|
|
let message = this.messageMap.get(data.uid);
|
|
|
|
|
const newMessage = !!message;
|
|
|
|
|
if (message) {
|
|
|
|
|
message.parentElement?.removeChild(message);
|
|
|
|
|
}
|
|
|
|
@@ -255,14 +296,14 @@ class MessageList extends HTMLElement {
|
|
|
|
|
if (message) {
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|