PageSourceSearch

https://pupwife.com/projects/chatroom/scripts/components/Message.js

js pupwife.com collected 2026-09-30 15:06:47 UTC 5,537 bytes, 136 lines download raw bytes

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.