/** * Message Content Processing Utilities * Handles mention badges, URL links, and image previews */ /** * Process message content to handle mentions, URLs, and images * @param {string} content - Raw message content * @returns {string} - Processed HTML content */ function processMessageContent(content) { if (!content) return ''; // First escape HTML to prevent XSS let processed = escapeHtml(content); // Process in order: // 1. Convert @[Username] mentions to badges processed = processMentions(processed); // 2. Convert URLs to links (and images to thumbnails) processed = processUrls(processed); return processed; } /** * Convert @[Username] mentions to styled badges * @param {string} text - HTML-escaped text * @returns {string} - Text with mention badges */ function processMentions(text) { // Match @[Username] pattern // Note: text is already HTML-escaped, so we match escaped brackets const mentionPattern = /@\[([^\]]+)\]/g; return text.replace(mentionPattern, (_match, username) => { // Create badge similar to Android Meshcore app return `@${username}`; }); } /** * Convert URLs to clickable links and images to thumbnails * @param {string} text - HTML-escaped text * @returns {string} - Text with links and image thumbnails */ function processUrls(text) { // URL regex pattern (handles http:// and https://) const urlPattern = /(https?:\/\/[^\s<>"{}|\\^`\[\]]+)/g; return text.replace(urlPattern, (url) => { // Check if URL is an image if (isImageUrl(url)) { return createImageThumbnail(url); } else { return createLink(url); } }); } /** * Check if URL points to an image * @param {string} url - URL to check * @returns {boolean} - True if URL is an image */ function isImageUrl(url) { const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp']; const urlLower = url.toLowerCase(); return imageExtensions.some(ext => urlLower.endsWith(ext)); } /** * Create a clickable link * @param {string} url - URL to link to * @returns {string} - HTML link element */ function createLink(url) { return `${url}`; } /** * Create an image thumbnail with click-to-expand * @param {string} url - Image URL * @returns {string} - HTML image thumbnail */ function createImageThumbnail(url) { // Escape URL for use in HTML attributes const escapedUrl = escapeHtmlAttribute(url); return `
Image
${url}
`; } /** * Show image in modal * @param {string} url - Image URL to display */ function showImageModal(url) { // Create modal if it doesn't exist let modal = document.getElementById('imagePreviewModal'); if (!modal) { modal = createImageModal(); document.body.appendChild(modal); } // Set image source const img = modal.querySelector('#imagePreviewImg'); if (img) { img.src = url; } // Show modal using Bootstrap const bsModal = new bootstrap.Modal(modal); bsModal.show(); } /** * Create image preview modal element * @returns {HTMLElement} - Modal element */ function createImageModal() { const modal = document.createElement('div'); modal.id = 'imagePreviewModal'; modal.className = 'modal fade'; modal.tabIndex = -1; modal.setAttribute('aria-labelledby', 'imagePreviewModalLabel'); modal.setAttribute('aria-hidden', 'true'); modal.innerHTML = ` `; return modal; } /** * Escape HTML to prevent XSS * @param {string} text - Text to escape * @returns {string} - Escaped text */ function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } /** * Escape HTML attribute to prevent XSS in attributes * @param {string} text - Text to escape * @returns {string} - Escaped text safe for HTML attributes */ function escapeHtmlAttribute(text) { if (!text) return ''; return text .replace(/&/g, '&') .replace(/"/g, '"') .replace(/'/g, ''') .replace(//g, '>'); } /** * Initialize image click handlers using event delegation * This should be called after DOM content is loaded */ function initializeImageHandlers() { // Use event delegation on document to handle dynamically added images document.addEventListener('click', function(e) { if (e.target.classList.contains('message-image-thumbnail')) { const url = e.target.getAttribute('data-image-url'); if (url) { showImageModal(url); } } }); } // Auto-initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializeImageHandlers); } else { // DOM already loaded initializeImageHandlers(); }