Update performance.
This commit is contained in:
parent
0bf714061c
commit
6a74263606
@ -19,340 +19,275 @@
|
|||||||
{% include "dialog_online.html" %}
|
{% include "dialog_online.html" %}
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { app } from "/app.js";
|
import { app } from "/app.js";
|
||||||
import { Schedule } from "/schedule.js";
|
import { Schedule } from "/schedule.js";
|
||||||
const channelUid = "{{ channel.uid.value }}";
|
|
||||||
const chatInputField = document.querySelector("chat-input");
|
// --- Cache selectors ---
|
||||||
chatInputField.autoCompletions = {
|
const chatInputField = document.querySelector("chat-input");
|
||||||
"/online": () =>{
|
const messagesContainer = document.querySelector(".chat-messages");
|
||||||
showOnline();
|
const chatArea = document.querySelector(".chat-area");
|
||||||
},
|
const channelUid = "{{ channel.uid.value }}";
|
||||||
"/clear": () => {
|
const username = "{{ user.username.value }}";
|
||||||
document.querySelector(".chat-messages").innerHTML = '';
|
|
||||||
},
|
// --- Command completions ---
|
||||||
"/live": () =>{
|
chatInputField.autoCompletions = {
|
||||||
|
"/online": showOnline,
|
||||||
chatInputField.liveType = !chatInputField.liveType
|
"/clear": () => { messagesContainer.innerHTML = ''; },
|
||||||
},
|
"/live": () => { chatInputField.liveType = !chatInputField.liveType; },
|
||||||
"/help": () => {
|
"/help": showHelp
|
||||||
showHelp();
|
};
|
||||||
|
|
||||||
|
// --- Throttle utility ---
|
||||||
|
function throttle(fn, wait) {
|
||||||
|
let last = 0;
|
||||||
|
return function(...args) {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - last >= wait) {
|
||||||
|
last = now;
|
||||||
|
fn.apply(this, args);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
app.ws.addEventListener("refresh", (data) => {
|
}
|
||||||
app.starField.showNotify(data.message);
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload();
|
|
||||||
},4000)
|
|
||||||
})
|
|
||||||
app.ws.addEventListener("deployed", (data) => {
|
|
||||||
app.starField.renderWord("Deployed",{"rainbow":true,"resolution":8});
|
|
||||||
setTimeout(() => {
|
|
||||||
app.starField.shuffleAll(5000);
|
|
||||||
},10000)
|
|
||||||
})
|
|
||||||
app.ws.addEventListener("starfield.render_word", (data) => {
|
|
||||||
app.starField.renderWord(data.word,data);
|
|
||||||
})
|
|
||||||
const textBox = document.querySelector("chat-input").textarea
|
|
||||||
textBox.addEventListener("paste", async (e) => {
|
|
||||||
try {
|
|
||||||
const clipboardItems = await navigator.clipboard.read();
|
|
||||||
|
|
||||||
// DataTransfer needs to be used to modify the files list of the input
|
// --- Scroll: load extra messages, throttled ---
|
||||||
const dt = new DataTransfer();
|
let isLoadingExtra = false;
|
||||||
|
async function loadExtra() {
|
||||||
for (const clipboardItem of clipboardItems) {
|
const firstMessage = messagesContainer.querySelector(".message:first-child");
|
||||||
const fileTypes = clipboardItem.types.filter(type => !type.startsWith('text/'))
|
if (isLoadingExtra || !isScrolledPastHalf() || !firstMessage) return;
|
||||||
for (const fileType of fileTypes) {
|
isLoadingExtra = true;
|
||||||
|
const messages = await app.rpc.getMessages(channelUid, 0, firstMessage.dataset.created_at);
|
||||||
const blob = await clipboardItem.getType(fileType);
|
if (messages.length) {
|
||||||
// Do something with the image blob.
|
const frag = document.createDocumentFragment();
|
||||||
dt.items.add(new File([blob], "image.png", { type: fileType }));
|
messages.forEach(msg => {
|
||||||
}
|
const temp = document.createElement("div");
|
||||||
}
|
temp.innerHTML = msg.html;
|
||||||
|
frag.appendChild(temp.firstChild);
|
||||||
if (dt.items.length > 0) {
|
|
||||||
const uploadButton = chatInputField.uploadButton
|
|
||||||
const input = uploadButton.shadowRoot.querySelector('.file-input')
|
|
||||||
input.files = dt.files;
|
|
||||||
|
|
||||||
await uploadButton.uploadFiles();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to read clipboard contents: ", error);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
firstMessage.parentNode.insertBefore(frag, firstMessage);
|
||||||
|
updateLayout(false);
|
||||||
|
|
||||||
const chatInput = document.querySelector(".chat-area")
|
|
||||||
chatInput.addEventListener("drop", async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const dt = e.dataTransfer;
|
|
||||||
if (dt.items.length > 0) {
|
|
||||||
const uploadButton = chatInputField.uploadButton
|
|
||||||
const input = uploadButton.shadowRoot.querySelector('.file-input')
|
|
||||||
input.files = dt.files;
|
|
||||||
|
|
||||||
await uploadButton.uploadFiles();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
chatInput.addEventListener("dragover", async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.dataTransfer.dropEffect = "link";
|
|
||||||
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
chatInputField.textarea.focus();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function replyMessage(message) {
|
|
||||||
const field = chatInputField
|
|
||||||
field.value = "```markdown\n> " + (message || '') + "\n```\n";
|
|
||||||
field.focus();
|
|
||||||
}
|
}
|
||||||
|
isLoadingExtra = false;
|
||||||
|
}
|
||||||
|
messagesContainer.addEventListener("scroll", throttle(loadExtra, 200));
|
||||||
|
|
||||||
function updateTimes() {
|
// --- Only update visible times ---
|
||||||
document.querySelectorAll(".time").forEach((container) => {
|
function updateTimes() {
|
||||||
|
const containers = messagesContainer.querySelectorAll(".time");
|
||||||
|
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
|
||||||
|
containers.forEach(container => {
|
||||||
|
const rect = container.getBoundingClientRect();
|
||||||
|
if (rect.top >= 0 && rect.bottom <= viewportHeight) {
|
||||||
const messageDiv = container.closest('.message');
|
const messageDiv = container.closest('.message');
|
||||||
const userNick = messageDiv.dataset.user_nick;
|
|
||||||
const text = messageDiv.querySelector(".text").innerText;
|
const text = messageDiv.querySelector(".text").innerText;
|
||||||
const time = document.createElement("span");
|
const time = document.createElement("span");
|
||||||
time.innerText = app.timeDescription(container.dataset.created_at);
|
time.innerText = app.timeDescription(container.dataset.created_at);
|
||||||
|
|
||||||
container.replaceChildren(time);
|
container.replaceChildren(time);
|
||||||
const reply = document.createElement("a");
|
const reply = document.createElement("a");
|
||||||
reply.innerText = " reply";
|
reply.innerText = " reply";
|
||||||
reply.href = "#reply";
|
reply.href = "#reply";
|
||||||
container.appendChild(reply);
|
container.appendChild(reply);
|
||||||
reply.addEventListener('click', (e) => {
|
reply.addEventListener('click', e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
replyMessage(text);
|
replyMessage(text);
|
||||||
})
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function isElementVisible(element) {
|
|
||||||
const rect = element.getBoundingClientRect();
|
|
||||||
return (
|
|
||||||
rect.top >= 0 &&
|
|
||||||
rect.left >= 0 &&
|
|
||||||
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
||||||
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const messagesContainer = document.querySelector(".chat-messages");
|
|
||||||
|
|
||||||
function isScrolledPastHalf() {
|
|
||||||
let scrollTop = messagesContainer.scrollTop;
|
|
||||||
let scrollableHeight = messagesContainer.scrollHeight - messagesContainer.clientHeight;
|
|
||||||
|
|
||||||
if (scrollTop < scrollableHeight / 2) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let isLoadingExtra = false;
|
|
||||||
|
|
||||||
async function loadExtra() {
|
|
||||||
const firstMessage = messagesContainer.querySelector(".message:first-child");
|
|
||||||
if (isLoadingExtra) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!isScrolledPastHalf()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoadingExtra = true;
|
|
||||||
|
|
||||||
const messages = await app.rpc.getMessages(channelUid, 0, firstMessage.dataset.created_at);
|
|
||||||
|
|
||||||
messages.forEach((message) => {
|
|
||||||
firstMessage.insertAdjacentHTML("beforebegin", message.html);
|
|
||||||
})
|
|
||||||
updateLayout(false);
|
|
||||||
|
|
||||||
isLoadingExtra = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
messagesContainer.addEventListener("scroll", () => {
|
|
||||||
loadExtra();
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
setInterval(() => requestIdleCallback(updateTimes), 30000);
|
||||||
|
|
||||||
let lastMessage
|
// --- Paste & drag/drop uploads ---
|
||||||
|
const textBox = chatInputField.textarea;
|
||||||
|
textBox.addEventListener("paste", async (e) => {
|
||||||
|
try {
|
||||||
|
const clipboardItems = await navigator.clipboard.read();
|
||||||
|
const dt = new DataTransfer();
|
||||||
|
for (const item of clipboardItems) {
|
||||||
|
for (const type of item.types.filter(t => !t.startsWith('text/'))) {
|
||||||
|
const blob = await item.getType(type);
|
||||||
|
dt.items.add(new File([blob], "image.png", { type }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dt.items.length > 0) {
|
||||||
|
const uploadButton = chatInputField.uploadButton;
|
||||||
|
const input = uploadButton.shadowRoot.querySelector('.file-input');
|
||||||
|
input.files = dt.files;
|
||||||
|
await uploadButton.uploadFiles();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to read clipboard contents: ", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
chatArea.addEventListener("drop", async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const dt = e.dataTransfer;
|
||||||
|
if (dt.items.length > 0) {
|
||||||
|
const uploadButton = chatInputField.uploadButton;
|
||||||
|
const input = uploadButton.shadowRoot.querySelector('.file-input');
|
||||||
|
input.files = dt.files;
|
||||||
|
await uploadButton.uploadFiles();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
chatArea.addEventListener("dragover", e => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = "link";
|
||||||
|
});
|
||||||
|
|
||||||
function updateLayout(doScrollDown) {
|
// --- Focus input on load ---
|
||||||
const messagesContainer = document.querySelector(".chat-messages");
|
chatInputField.textarea.focus();
|
||||||
updateTimes();
|
|
||||||
let previousUser = null;
|
// --- Reply helper ---
|
||||||
let previousDate = null;
|
function replyMessage(message) {
|
||||||
document.querySelectorAll(".message").forEach((message) => {
|
chatInputField.value = "markdown\n> " + (message || '') + "\n";
|
||||||
if (previousUser !== message.dataset.user_uid) {
|
chatInputField.focus();
|
||||||
message.classList.add("switch-user");
|
}
|
||||||
previousUser = message.dataset.user_uid;
|
|
||||||
|
// --- Mention helpers ---
|
||||||
|
function extractMentions(message) {
|
||||||
|
return [...new Set(message.match(/@\w+/g) || [])];
|
||||||
|
}
|
||||||
|
function isMentionToMe(message) {
|
||||||
|
return message.toLowerCase().includes('@' + username.toLowerCase());
|
||||||
|
}
|
||||||
|
function isMentionForSomeoneElse(message) {
|
||||||
|
const mentions = extractMentions(message);
|
||||||
|
return mentions.length > 0 && !mentions.includes('@' + username);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- WebSocket events ---
|
||||||
|
app.ws.addEventListener("refresh", (data) => {
|
||||||
|
app.starField.showNotify(data.message);
|
||||||
|
setTimeout(() => window.location.reload(), 4000);
|
||||||
|
});
|
||||||
|
app.ws.addEventListener("deployed", (data) => {
|
||||||
|
app.starField.renderWord("Deployed", { rainbow: true, resolution: 8 });
|
||||||
|
setTimeout(() => app.starField.shuffleAll(5000), 10000);
|
||||||
|
});
|
||||||
|
app.ws.addEventListener("starfield.render_word", (data) => {
|
||||||
|
app.starField.renderWord(data.word, data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Channel message event ---
|
||||||
|
app.addEventListener("channel-message", (data) => {
|
||||||
|
let display = data.text && data.text.trim() ? 'block' : 'none';
|
||||||
|
if (data.channel_uid !== channelUid) {
|
||||||
|
if (!isMentionForSomeoneElse(data.message)) {
|
||||||
|
channelSidebar.notify(data);
|
||||||
|
app.playSound("messageOtherChannel");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.username !== username) {
|
||||||
|
if (isMentionToMe(data.message)) {
|
||||||
|
app.playSound("mention");
|
||||||
|
} else if (!isMentionForSomeoneElse(data.message)) {
|
||||||
|
app.playSound("message");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const lastMessage = messagesContainer.querySelector(".message:last-child");
|
||||||
|
const lastElement = messagesContainer.querySelector(".message-list-bottom");
|
||||||
|
const doScrollDown = !lastMessage || isElementVisible(lastMessage);
|
||||||
|
const message = document.createElement("div");
|
||||||
|
message.innerHTML = data.html;
|
||||||
|
message.style.display = display;
|
||||||
|
messagesContainer.insertBefore(message.firstChild, lastElement);
|
||||||
|
updateLayout(doScrollDown);
|
||||||
|
setTimeout(() => updateLayout(doScrollDown), 1000);
|
||||||
|
app.rpc.markAsRead(channelUid);
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Keyboard shortcuts ---
|
||||||
|
let escPressed = false, gPressCount = 0, keyTimeout;
|
||||||
|
document.addEventListener('keydown', function(event) {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
escPressed = true; gPressCount = 0;
|
||||||
|
clearTimeout(keyTimeout);
|
||||||
|
keyTimeout = setTimeout(() => { escPressed = false; }, 300);
|
||||||
|
}
|
||||||
|
if (event.key === 'G' && escPressed) {
|
||||||
|
gPressCount++;
|
||||||
|
clearTimeout(keyTimeout);
|
||||||
|
keyTimeout = setTimeout(() => { gPressCount = 0; }, 300);
|
||||||
|
if (gPressCount === 2) {
|
||||||
|
gPressCount = 0; escPressed = false;
|
||||||
|
messagesContainer.querySelector(".message:last-child")?.scrollIntoView({ block: "end", inline: "nearest" });
|
||||||
|
setTimeout(() => chatInputField.focus(), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (event.shiftKey && event.key === 'G') {
|
||||||
|
if (chatInputField.isActive()) {
|
||||||
|
updateLayout(true);
|
||||||
|
setTimeout(() => chatInputField.focus(), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Image click-to-zoom (delegated) ---
|
||||||
|
messagesContainer.addEventListener('click', (e) => {
|
||||||
|
if (e.target.tagName !== 'IMG' || e.target.classList.contains('avatar')) return;
|
||||||
|
const img = e.target;
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);display:flex;justify-content:center;align-items:center;z-index:9999;'
|
||||||
|
const fullImg = document.createElement('img');
|
||||||
|
const urlObj = new URL(img.src); urlObj.search = '';
|
||||||
|
fullImg.src = urlObj.toString();
|
||||||
|
fullImg.alt = img.alt;
|
||||||
|
fullImg.style.maxWidth = '90%';
|
||||||
|
fullImg.style.maxHeight = '90%';
|
||||||
|
overlay.appendChild(fullImg);
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
overlay.addEventListener('click', () => document.body.removeChild(overlay));
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Layout update ---
|
||||||
|
let lastMessage;
|
||||||
|
function updateLayout(doScrollDown) {
|
||||||
|
updateTimes();
|
||||||
|
let previousUser = null, previousDate = null;
|
||||||
|
messagesContainer.querySelectorAll(".message").forEach((message) => {
|
||||||
|
if (previousUser !== message.dataset.user_uid) {
|
||||||
|
message.classList.add("switch-user");
|
||||||
|
previousUser = message.dataset.user_uid;
|
||||||
|
previousDate = new Date(message.dataset.created_at);
|
||||||
|
} else {
|
||||||
|
message.classList.remove("switch-user");
|
||||||
|
if (!previousDate) {
|
||||||
previousDate = new Date(message.dataset.created_at);
|
previousDate = new Date(message.dataset.created_at);
|
||||||
} else {
|
} else {
|
||||||
message.classList.remove("switch-user");
|
const currentDate = new Date(message.dataset.created_at);
|
||||||
|
if (currentDate.getTime() - previousDate.getTime() > 1000 * 60 * 20) {
|
||||||
if (!previousDate) {
|
message.classList.add("long-time");
|
||||||
previousDate = new Date(message.dataset.created_at);
|
|
||||||
} else {
|
} else {
|
||||||
const currentDate = new Date(message.dataset.created_at);
|
message.classList.remove("long-time");
|
||||||
|
|
||||||
if (currentDate.getTime() - previousDate.getTime() > 1000 * 60 * 20) {
|
|
||||||
message.classList.add("long-time");
|
|
||||||
} else {
|
|
||||||
message.classList.remove("long-time");
|
|
||||||
}
|
|
||||||
previousDate = currentDate;
|
|
||||||
}
|
}
|
||||||
|
previousDate = currentDate;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
lastMessage = messagesContainer.querySelector(".message:last-child");
|
|
||||||
if (doScrollDown) {
|
|
||||||
messagesContainer.scrollToBottom()
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setInterval(updateTimes, 30000);
|
|
||||||
|
|
||||||
function isMentionToMe(message){
|
|
||||||
const mentionText = '@{{ user.username.value }}';
|
|
||||||
return message.toLowerCase().includes(mentionText);
|
|
||||||
}
|
|
||||||
function extractMentions(message) {
|
|
||||||
return [...new Set(message.match(/@\w+/g) || [])];
|
|
||||||
}
|
|
||||||
function isMentionForSomeoneElse(message){
|
|
||||||
const mentions = extractMentions(message);
|
|
||||||
const mentionText = '@{{ user.username.value }}';
|
|
||||||
return mentions.length > 0 && mentions.indexOf(mentionText) == -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
app.addEventListener("channel-message", (data) => {
|
|
||||||
let display = 'block';
|
|
||||||
if(!data.text || !data.text.trim()){
|
|
||||||
display = "none";
|
|
||||||
}
|
|
||||||
if (data.channel_uid !== channelUid) {
|
|
||||||
if(!isMentionForSomeoneElse(data.message)){
|
|
||||||
channelSidebar.notify(data);
|
|
||||||
app.playSound("messageOtherChannel");
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (data.username !== "{{ user.username.value }}") {
|
|
||||||
if(isMentionToMe(data.message)){
|
|
||||||
app.playSound("mention");
|
|
||||||
}else if (!isMentionForSomeoneElse(data.message)){
|
|
||||||
app.playSound("message");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const messagesContainer = document.querySelector(".chat-messages");
|
|
||||||
lastMessage = messagesContainer.querySelector(".message:last-child");
|
|
||||||
const lastElement = messagesContainer.querySelector(".message-list-bottom");
|
|
||||||
const doScrollDownBecauseLastMessageIsVisible = !lastMessage || isElementVisible(lastMessage);
|
|
||||||
|
|
||||||
const message = document.createElement("div");
|
|
||||||
message.innerHTML = data.html;
|
|
||||||
message.style.display = display
|
|
||||||
document.querySelector(".chat-messages").insertBefore(message.firstChild,lastElement);
|
|
||||||
updateLayout(doScrollDownBecauseLastMessageIsVisible);
|
|
||||||
setTimeout(() => {
|
|
||||||
updateLayout(doScrollDownBecauseLastMessageIsVisible)
|
|
||||||
}, 1000);
|
|
||||||
app.rpc.markAsRead(channelUid);
|
|
||||||
});
|
|
||||||
|
|
||||||
let escPressed = false;
|
|
||||||
let gPressCount = 0;
|
|
||||||
let keyTimeout;
|
|
||||||
document.addEventListener('keydown', function(event) {
|
|
||||||
|
|
||||||
if (event.key === 'Escape') {
|
|
||||||
escPressed = true;
|
|
||||||
gPressCount = 0;
|
|
||||||
clearTimeout(keyTimeout);
|
|
||||||
keyTimeout = setTimeout(() => {
|
|
||||||
escPressed = false;
|
|
||||||
}, 300);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.key === 'G' && escPressed) {
|
|
||||||
gPressCount++;
|
|
||||||
|
|
||||||
clearTimeout(keyTimeout);
|
|
||||||
keyTimeout = setTimeout(() => {
|
|
||||||
gPressCount = 0;
|
|
||||||
}, 300);
|
|
||||||
if (gPressCount === 2) {
|
|
||||||
gPressCount = 0;
|
|
||||||
escPressed = false;
|
|
||||||
|
|
||||||
messagesContainer.querySelector(".message:last-child").scrollIntoView({ block: "end", inline: "nearest" });
|
|
||||||
setTimeout(() => {
|
|
||||||
|
|
||||||
chatInputField.focus();
|
|
||||||
},500)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (event.shiftKey && event.key === 'G') {
|
|
||||||
if(chatInputField.isActive()){
|
|
||||||
|
|
||||||
updateLayout(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
chatInputField.focus();
|
|
||||||
},500)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
lastMessage = messagesContainer.querySelector(".message:last-child");
|
||||||
|
if (doScrollDown) messagesContainer.scrollToBottom?.();
|
||||||
|
}
|
||||||
|
|
||||||
messagesContainer.addEventListener('click', (e) => {
|
// --- Utility: check if element is visible ---
|
||||||
if(e.target.tagName != 'IMG')
|
function isElementVisible(element) {
|
||||||
return
|
const rect = element.getBoundingClientRect();
|
||||||
const img = e.target
|
return (
|
||||||
if(e.target.classList.contains('avatar')){
|
rect.top >= 0 &&
|
||||||
return
|
rect.left >= 0 &&
|
||||||
}
|
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
||||||
const overlay = document.createElement('div');
|
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
||||||
overlay.style.position = 'fixed';
|
);
|
||||||
overlay.style.top = 0;
|
}
|
||||||
overlay.style.left = 0;
|
|
||||||
overlay.style.width = '100%';
|
|
||||||
overlay.style.height = '100%';
|
|
||||||
overlay.style.backgroundColor = 'rgba(0,0,0,0.9)';
|
|
||||||
overlay.style.display = 'flex';
|
|
||||||
overlay.style.justifyContent = 'center';
|
|
||||||
overlay.style.alignItems = 'center';
|
|
||||||
overlay.style.zIndex = 9999;
|
|
||||||
|
|
||||||
const fullImg = document.createElement('img');
|
// --- Utility: check if scrolled past half ---
|
||||||
|
function isScrolledPastHalf() {
|
||||||
|
let scrollTop = messagesContainer.scrollTop;
|
||||||
|
let scrollableHeight = messagesContainer.scrollHeight - messagesContainer.clientHeight;
|
||||||
|
return scrollTop < scrollableHeight / 2;
|
||||||
|
}
|
||||||
|
|
||||||
const urlObj = new URL(img.src);
|
// --- Initial layout update ---
|
||||||
urlObj.search = ''
|
updateLayout(true);
|
||||||
fullImg.src = urlObj.toString();
|
|
||||||
|
|
||||||
fullImg.alt = img.alt;
|
|
||||||
fullImg.style.maxWidth = '90%';
|
|
||||||
fullImg.style.maxHeight = '90%';
|
|
||||||
|
|
||||||
overlay.appendChild(fullImg);
|
|
||||||
|
|
||||||
document.body.appendChild(overlay);
|
|
||||||
|
|
||||||
overlay.addEventListener('click', () => {
|
|
||||||
document.body.removeChild(overlay);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
updateLayout(true);
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Loading…
Reference in New Issue
Block a user