Update performance.

This commit is contained in:
retoor 2025-05-24 14:18:38 +02:00
parent 0bf714061c
commit 6a74263606

View File

@ -19,340 +19,275 @@
{% include "dialog_online.html" %}
<script type="module">
import { app } from "/app.js";
import { Schedule } from "/schedule.js";
const channelUid = "{{ channel.uid.value }}";
const chatInputField = document.querySelector("chat-input");
chatInputField.autoCompletions = {
"/online": () =>{
showOnline();
},
"/clear": () => {
document.querySelector(".chat-messages").innerHTML = '';
},
"/live": () =>{
chatInputField.liveType = !chatInputField.liveType
},
"/help": () => {
showHelp();
import { Schedule } from "/schedule.js";
// --- Cache selectors ---
const chatInputField = document.querySelector("chat-input");
const messagesContainer = document.querySelector(".chat-messages");
const chatArea = document.querySelector(".chat-area");
const channelUid = "{{ channel.uid.value }}";
const username = "{{ user.username.value }}";
// --- Command completions ---
chatInputField.autoCompletions = {
"/online": showOnline,
"/clear": () => { messagesContainer.innerHTML = ''; },
"/live": () => { chatInputField.liveType = !chatInputField.liveType; },
"/help": 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
const dt = new DataTransfer();
for (const clipboardItem of clipboardItems) {
const fileTypes = clipboardItem.types.filter(type => !type.startsWith('text/'))
for (const fileType of fileTypes) {
const blob = await clipboardItem.getType(fileType);
// Do something with the image blob.
dt.items.add(new File([blob], "image.png", { type: fileType }));
}
}
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);
}
// --- Scroll: load extra messages, throttled ---
let isLoadingExtra = false;
async function loadExtra() {
const firstMessage = messagesContainer.querySelector(".message:first-child");
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.forEach(msg => {
const temp = document.createElement("div");
temp.innerHTML = msg.html;
frag.appendChild(temp.firstChild);
});
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();
firstMessage.parentNode.insertBefore(frag, firstMessage);
updateLayout(false);
}
isLoadingExtra = false;
}
messagesContainer.addEventListener("scroll", throttle(loadExtra, 200));
function updateTimes() {
document.querySelectorAll(".time").forEach((container) => {
// --- Only update visible times ---
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 userNick = messageDiv.dataset.user_nick;
const text = messageDiv.querySelector(".text").innerText;
const time = document.createElement("span");
time.innerText = app.timeDescription(container.dataset.created_at);
container.replaceChildren(time);
const reply = document.createElement("a");
reply.innerText = " reply";
reply.href = "#reply";
container.appendChild(reply);
reply.addEventListener('click', (e) => {
reply.addEventListener('click', e => {
e.preventDefault();
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) {
const messagesContainer = document.querySelector(".chat-messages");
updateTimes();
let previousUser = null;
let previousDate = null;
document.querySelectorAll(".message").forEach((message) => {
if (previousUser !== message.dataset.user_uid) {
message.classList.add("switch-user");
previousUser = message.dataset.user_uid;
// --- Focus input on load ---
chatInputField.textarea.focus();
// --- Reply helper ---
function replyMessage(message) {
chatInputField.value = "markdown\n> " + (message || '') + "\n";
chatInputField.focus();
}
// --- 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);
} else {
message.classList.remove("switch-user");
if (!previousDate) {
previousDate = new Date(message.dataset.created_at);
const currentDate = new Date(message.dataset.created_at);
if (currentDate.getTime() - previousDate.getTime() > 1000 * 60 * 20) {
message.classList.add("long-time");
} else {
const currentDate = new Date(message.dataset.created_at);
if (currentDate.getTime() - previousDate.getTime() > 1000 * 60 * 20) {
message.classList.add("long-time");
} else {
message.classList.remove("long-time");
}
previousDate = currentDate;
message.classList.remove("long-time");
}
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) => {
if(e.target.tagName != 'IMG')
return
const img = e.target
if(e.target.classList.contains('avatar')){
return
}
const overlay = document.createElement('div');
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;
// --- Utility: check if element is visible ---
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 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);
urlObj.search = ''
fullImg.src = urlObj.toString();
// --- Initial layout update ---
updateLayout(true);
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>
{% endblock %}