Update performance.
This commit is contained in:
		
							parent
							
								
									0bf714061c
								
							
						
					
					
						commit
						6a74263606
					
				| @ -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 %} | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user