1/** 2 * Message Component (Vencord-inspired) 3 * Reusable message rendering component 4 */ 5 6window.ChatroomMessageComponent = (function() { 7 'use strict'; 8 9 /** 10 * Create message element 11 */ 12 function create(message, options = {}) { 13 const { 14 grouped = false, 15 showAvatar = true, 16 showTimestamp = true, 17 showUsername = true 18 } = options; 19 20 const messageDiv = document.createElement('div'); 21 messageDiv.className = 'message'; 22 if (grouped) messageDiv.classList.add('grouped'); 23 const text = message.text != null ? String(message.text) : ''; 24 if (text && !(message.images && message.images.length > 0) && window.ChatroomMessageFormatter?.shouldScaleEmojiMessage?.(text)) { 25 messageDiv.classList.add('message-emoji-only'); 26 } 27 messageDiv.dataset.messageId = message.id; 28 if (window.ChatroomMessageGrouping && window.ChatroomMessageGrouping.applyMetaToElement) { 29 window.ChatroomMessageGrouping.applyMetaToElement(messageDiv, message); 30 } 31 32 // Check if user is moderator (guard against missing username) 33 const username = message.username != null && String(message.username).trim() ? String(message.username).trim() : 'Someone'; 34 const isMod = window.ChatroomModerator && window.ChatroomModerator.isUserModerator(username); 35 const modBadge = isMod ? '<span class="mod-badge" title="Moderator">ð</span>' : ''; 36 37 // Format message text (guard against missing text) 38 const formattedText = window.ChatroomMessageFormatter 39 ? window.ChatroomMessageFormatter.formatMessage(text) 40 : escapeHtml(text); 41 42 // Format timestamp 43 const formattedTime = window.ChatroomMessageFormatter 44 ? window.ChatroomMessageFormatter.formatTimestamp(message.timestamp) 45 : formatTime(message.timestamp); 46 47 // Build images HTML if present 48 let imagesHTML = ''; 49 if (message.images && message.images.length > 0) { 50 imagesHTML = '<div class="message-images">'; 51 message.images.forEach((image, index) => { 52 const imageData = typeof image === 'string' ? image : (image.data || image); 53 // Base64 data URLs are safe, but we should still escape for safety 54 const safeImageData = imageData; 55 imagesHTML += ` 56 <div class="message-image-container"> 57 <img src="${safeImageData}" alt="Image ${index + 1}" class="message-image" loading="lazy"> 58 <a href="${safeImageData}" target="_blank" rel="noopener noreferrer" class="message-image-link" title="Open image in new tab">ð</a> 59 </div> 60 `; 61 }); 62 imagesHTML += '</div>'; 63 } 64 65 const replyHTML = message.replyTo && window.ChatroomReplies 66 ? window.ChatroomReplies.buildReplyReferenceHTML(message.replyTo) 67 : ''; 68 69 messageDiv.innerHTML = ` 70 ${showAvatar ? `<div class="message-avatar">${getAvatar(username)}</div>` : ''} 71 <div class="message-content"> 72 ${showUsername ? ` 73 <div class="message-header"> 74 <span class="message-username">${modBadge}${escapeHtml(username)}</span> 75 ${showTimestamp ? `<span class="message-time">${formattedTime}</span>` : ''} 76 </div> 77 ` : ''} 78 ${replyHTML} 79 ${formattedText ? `<div class="message-text">${formattedText}</div>` : ''} 80 ${imagesHTML} 81 <div class="message-reactions"></div> 82 </div> 83 `; 84 85 if (window.ChatroomReactions && window.ChatroomReactions.initMessageReactions) { 86 window.ChatroomReactions.initMessageReactions(messageDiv); 87 } 88 89 if (window.ChatroomMessageActions && window.ChatroomMessageActions.setupMessageHover) { 90 window.ChatroomMessageActions.setupMessageHover(messageDiv); 91 } 92 93 return messageDiv; 94 } 95 96 /** 97 * Get avatar emoji 98 */ 99 function getAvatar(username) { 100 if (username == null || typeof username !== 'string') { 101 return 'ð¾'; // fallback for missing/malformed usernames 102 } 103 const avatars = ['ð¾', 'ð¶', 'ð', 'ð¦´', 'ð¾', 'ð§¸', 'â', 'ð«', 'â¨', 'ð']; 104 const hash = username.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0); 105 return avatars[hash % avatars.length]; 106 } 107 108 /** 109 * Format timestamp 110 */ 111 function formatTime(timestamp) { 112 const date = new Date(timestamp); 113 if (Number.isNaN(date.getTime())) return ''; 114 const now = new Date(); 115 const diff = now - date; 116 117 if (diff < 60000) return 'just now'; 118 if (diff < 3600000) return Math.floor(diff / 60000) + 'm ago'; 119 if (diff < 86400000) return Math.floor(diff / 3600000) + 'h ago'; 120 121 return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); 122 } 123 124 /** 125 * Escape HTML 126 */ 127 function escapeHtml(text) { 128 const div = document.createElement('div'); 129 div.textContent = text; 130 return div.innerHTML; 131 } 132 133 return { 134 create 135 }; 136})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.