Spaces:
Sleeping
Sleeping
// Chat functionality | |
let conversations = []; | |
let messages = {}; | |
let pollingInterval; | |
// Mobile sidebar functionality | |
function toggleMobileSidebar() { | |
const sidebar = document.getElementById('sidebar'); | |
const overlay = document.getElementById('sidebarOverlay'); | |
if (sidebar && overlay) { | |
sidebar.classList.toggle('show'); | |
overlay.classList.toggle('show'); | |
// Prevent body scroll when sidebar is open | |
if (sidebar.classList.contains('show')) { | |
document.body.style.overflow = 'hidden'; | |
} else { | |
document.body.style.overflow = ''; | |
} | |
} | |
} | |
// Close mobile sidebar when clicking outside | |
function closeMobileSidebar() { | |
const sidebar = document.getElementById('sidebar'); | |
const overlay = document.getElementById('sidebarOverlay'); | |
if (sidebar && overlay) { | |
sidebar.classList.remove('show'); | |
overlay.classList.remove('show'); | |
document.body.style.overflow = ''; | |
} | |
} | |
// Initialize chat functionality | |
document.addEventListener('DOMContentLoaded', () => { | |
if (!document.body.classList.contains('chat-page')) { | |
return; | |
} | |
initializeChat(); | |
}); | |
async function initializeChat() { | |
try { | |
console.log('Initializing chat...'); | |
// Clear any existing data | |
conversations = []; | |
messages = {}; | |
window.currentConversation = null; | |
// Load fresh data | |
await loadConversations(); | |
startPolling(); | |
setupEventListeners(); | |
console.log('Chat initialized successfully'); | |
} catch (error) { | |
console.error('Failed to initialize chat:', error); | |
MainJS.showError('Failed to initialize chat'); | |
} | |
} | |
function setupEventListeners() { | |
// Message form | |
const messageForm = document.getElementById('messageForm'); | |
if (messageForm) { | |
messageForm.addEventListener('submit', handleSendMessage); | |
} | |
// Private chat form | |
const privateChatForm = document.getElementById('privateChatForm'); | |
if (privateChatForm) { | |
privateChatForm.addEventListener('submit', handleStartPrivateChat); | |
} | |
// Group chat form | |
const groupChatForm = document.getElementById('groupChatForm'); | |
if (groupChatForm) { | |
groupChatForm.addEventListener('submit', handleCreateGroup); | |
} | |
} | |
async function loadConversations() { | |
try { | |
const response = await MainJS.apiRequest('/api/conversations'); | |
if (response.success) { | |
conversations = response.conversations || []; | |
renderConversations(); | |
} else { | |
console.warn('Failed to load conversations:', response.message); | |
// Show empty state instead of error for unauthenticated users | |
conversations = []; | |
renderConversations(); | |
} | |
} catch (error) { | |
console.error('Failed to load conversations:', error); | |
conversations = []; | |
renderConversations(); | |
} | |
} | |
function renderConversations() { | |
const conversationsList = document.getElementById('conversationsList'); | |
if (!conversationsList) { | |
console.error('Conversations list element not found'); | |
return; | |
} | |
console.log('Rendering conversations:', conversations); | |
// FORCE CLEAR the conversations list first | |
conversationsList.innerHTML = ''; | |
if (conversations.length === 0) { | |
conversationsList.innerHTML = ` | |
<div class="text-center p-4 text-muted"> | |
<i class="fas fa-comments mb-3" style="font-size: 2rem;"></i> | |
<p>No conversations yet</p> | |
<small>Start a new chat to begin messaging</small> | |
</div> | |
`; | |
console.log('No conversations to display'); | |
return; | |
} | |
conversationsList.innerHTML = conversations.map(conv => { | |
const lastMessage = conv.last_message; | |
const isActive = window.currentConversation === conv.id; | |
return ` | |
<div class="conversation-item ${isActive ? 'active' : ''}" onclick="selectConversation('${conv.id}')"> | |
<div class="d-flex align-items-center"> | |
<div class="avatar bg-success me-3 position-relative"> | |
${conv.type === 'group' ? '<i class="fas fa-users"></i>' : conv.name[0].toUpperCase()} | |
${conv.type === 'private' && conv.online ? '<div class="online-indicator"></div>' : ''} | |
</div> | |
<div class="flex-grow-1"> | |
<div class="d-flex justify-content-between align-items-start"> | |
<div class="fw-bold">${MainJS.escapeHtml(conv.name)}</div> | |
${lastMessage ? `<small class="text-muted">${MainJS.formatTime(lastMessage.timestamp)}</small>` : ''} | |
</div> | |
${lastMessage ? ` | |
<div class="text-muted small text-truncate"> | |
${conv.type === 'group' ? `${MainJS.escapeHtml(lastMessage.sender_name)}: ` : ''} | |
${MainJS.escapeHtml(lastMessage.content)} | |
</div> | |
` : '<div class="text-muted small">No messages yet</div>'} | |
${conv.type === 'private' && !conv.online ? '<div class="text-offline small">offline</div>' : ''} | |
</div> | |
</div> | |
</div> | |
`; | |
}).join(''); | |
} | |
async function selectConversation(conversationId) { | |
try { | |
console.log('Selecting conversation:', conversationId); | |
// Validate that conversation exists | |
const conversation = conversations.find(c => c.id === conversationId); | |
if (!conversation) { | |
console.error('Conversation not found:', conversationId); | |
MainJS.showError('Conversation not found. Please refresh and try again.'); | |
return; | |
} | |
window.currentConversation = conversationId; | |
// Update UI | |
document.querySelectorAll('.conversation-item').forEach(item => { | |
item.classList.remove('active'); | |
}); | |
// Find and activate the clicked conversation | |
const clickedItem = document.querySelector(`[onclick*="${conversationId}"]`); | |
if (clickedItem) { | |
clickedItem.classList.add('active'); | |
} | |
// Show chat container | |
const welcomeScreen = document.getElementById('welcomeScreen'); | |
const chatContainer = document.getElementById('chatContainer'); | |
if (welcomeScreen) welcomeScreen.style.display = 'none'; | |
if (chatContainer) { | |
chatContainer.style.display = 'flex'; | |
console.log('Chat container displayed'); | |
} else { | |
console.error('Chat container not found'); | |
} | |
// Close mobile sidebar when conversation is selected | |
if (window.innerWidth < 768) { | |
closeMobileSidebar(); | |
} | |
// Update chat header first | |
updateChatHeader(conversationId); | |
// Load conversation details with local storage for instant loading | |
console.log('Loading messages for conversation:', conversationId); | |
await loadMessagesWithLocalStorage(conversationId); | |
// Mark messages as seen | |
markMessagesAsSeen(conversationId); | |
console.log('Conversation selected successfully'); | |
} catch (error) { | |
console.error('Error selecting conversation:', error); | |
MainJS.showError('Failed to load conversation'); | |
} | |
} | |
async function loadMessages(conversationId) { | |
try { | |
console.log('Loading messages for conversation ID:', conversationId); | |
const response = await MainJS.apiRequest(`/api/messages/${conversationId}`); | |
console.log('Messages API response:', response); | |
if (response.success) { | |
messages[conversationId] = response.messages || []; | |
console.log('Messages loaded:', response.messages.length); | |
// Save messages to local storage | |
saveMessagesToLocalStorage(conversationId, messages[conversationId]); | |
renderMessages(conversationId); | |
} else { | |
console.error('API error:', response.message); | |
// Even if API fails, show the chat interface with empty state | |
messages[conversationId] = []; | |
renderMessages(conversationId); | |
MainJS.showError('Failed to load messages: ' + response.message); | |
} | |
} catch (error) { | |
console.error('Failed to load messages:', error); | |
// Show empty chat interface even on error | |
messages[conversationId] = []; | |
renderMessages(conversationId); | |
MainJS.showError('Connection error while loading messages'); | |
} | |
} | |
function renderMessages(conversationId) { | |
console.log('Rendering messages for conversation:', conversationId); | |
const chatMessages = document.getElementById('chatMessages'); | |
console.log('Chat messages element:', chatMessages); | |
console.log('Messages data:', messages[conversationId]); | |
if (!chatMessages) { | |
console.error('Chat messages element not found!'); | |
return; | |
} | |
if (!messages[conversationId]) { | |
console.error('No messages found for conversation:', conversationId); | |
return; | |
} | |
const conversationMessages = messages[conversationId]; | |
console.log('Number of messages to render:', conversationMessages.length); | |
if (conversationMessages.length === 0) { | |
console.log('No messages, showing empty state'); | |
chatMessages.innerHTML = ` | |
<div class="text-center text-muted"> | |
<i class="fas fa-comment-dots mb-2" style="font-size: 2rem;"></i> | |
<p>No messages yet</p> | |
<small>Send the first message to start the conversation</small> | |
</div> | |
`; | |
return; | |
} | |
chatMessages.innerHTML = conversationMessages.map(msg => { | |
const isCurrentUser = msg.sender_id === getCurrentUserId(); | |
const messageClass = isCurrentUser ? 'sent' : 'received'; | |
// Render different message types | |
if (msg.message_type === 'image') { | |
return renderImageMessage(msg, messageClass); | |
} else if (msg.message_type === 'file') { | |
return renderFileMessage(msg, messageClass); | |
} else if (msg.message_type === 'audio') { | |
return renderAudioMessage(msg, messageClass); | |
} else { | |
return renderTextMessage(msg, messageClass); | |
} | |
}).join(''); | |
// Scroll to bottom | |
chatMessages.scrollTop = chatMessages.scrollHeight; | |
// Mark messages as seen when viewing conversation | |
setTimeout(() => markVisibleMessagesAsSeen(), 500); | |
} | |
function renderTextMessage(msg, messageClass) { | |
const isCurrentUser = messageClass === 'sent'; | |
return ` | |
<div class="message-item ${messageClass}"> | |
<div class="message-content"> | |
${!isCurrentUser && getConversationType(window.currentConversation) === 'group' ? | |
`<div class="small text-muted mb-1">${MainJS.escapeHtml(msg.sender_name)}</div>` : ''} | |
<div class="message-bubble"> | |
${MainJS.escapeHtml(msg.content)} | |
</div> | |
<div class="message-time"> | |
${MainJS.formatMessageTime(msg.timestamp)} | |
${isCurrentUser ? getMessageStatusIcon(msg) : ''} | |
</div> | |
</div> | |
</div> | |
`; | |
} | |
function renderImageMessage(msg, messageClass) { | |
const isCurrentUser = messageClass === 'sent'; | |
return ` | |
<div class="message-item ${messageClass}"> | |
<div class="message-content"> | |
${!isCurrentUser && getConversationType(window.currentConversation) === 'group' ? | |
`<div class="small text-muted mb-1">${MainJS.escapeHtml(msg.sender_name)}</div>` : ''} | |
<div class="image-message ${messageClass}" onclick="openImagePreview('${msg.id}')"> | |
<img src="/api/image/${msg.id}" alt="${MainJS.escapeHtml(msg.file_name || 'Image')}" | |
class="message-image" loading="lazy"> | |
<div class="image-overlay"> | |
<i class="fas fa-search-plus"></i> | |
</div> | |
</div> | |
<div class="message-time"> | |
${MainJS.formatMessageTime(msg.timestamp)} | |
${isCurrentUser ? getMessageStatusIcon(msg) : ''} | |
</div> | |
</div> | |
</div> | |
`; | |
} | |
function renderFileMessage(msg, messageClass) { | |
const isCurrentUser = messageClass === 'sent'; | |
const iconClass = MainJS.getFileIconClass(msg.file_type || ''); | |
const iconColor = MainJS.getFileIconColor(msg.file_type || ''); | |
return ` | |
<div class="message-item ${messageClass}"> | |
<div class="message-content"> | |
${!isCurrentUser && getConversationType(window.currentConversation) === 'group' ? | |
`<div class="small text-muted mb-1">${MainJS.escapeHtml(msg.sender_name)}</div>` : ''} | |
<div class="file-message ${messageClass}" onclick="downloadFile('${msg.id}')"> | |
<div class="file-info"> | |
<div class="file-icon ${iconColor}"> | |
<i class="${iconClass}"></i> | |
</div> | |
<div class="file-details"> | |
<div class="file-name">${MainJS.escapeHtml(msg.file_name || 'Unknown File')}</div> | |
<div class="file-size">${msg.file_size_formatted || '0B'}</div> | |
</div> | |
</div> | |
</div> | |
<div class="message-time"> | |
${MainJS.formatMessageTime(msg.timestamp)} | |
${isCurrentUser ? getMessageStatusIcon(msg) : ''} | |
</div> | |
</div> | |
</div> | |
`; | |
} | |
function renderAudioMessage(msg, messageClass) { | |
const isCurrentUser = messageClass === 'sent'; | |
const duration = msg.audio_duration ? Math.floor(msg.audio_duration) : 0; | |
const minutes = Math.floor(duration / 60); | |
const seconds = duration % 60; | |
const durationText = `${minutes}:${seconds.toString().padStart(2, '0')}`; | |
return ` | |
<div class="message-item ${messageClass}"> | |
<div class="message-content"> | |
${!isCurrentUser && getConversationType(window.currentConversation) === 'group' ? | |
`<div class="small text-muted mb-1">${MainJS.escapeHtml(msg.sender_name)}</div>` : ''} | |
<div class="audio-message ${messageClass}"> | |
<div class="audio-controls"> | |
<button class="audio-play-btn" onclick="playAudioMessage('${msg.id}')"> | |
<i class="fas fa-play"></i> | |
</button> | |
<div class="audio-waveform"></div> | |
<div class="audio-duration">${durationText}</div> | |
</div> | |
</div> | |
<div class="message-time"> | |
${MainJS.formatMessageTime(msg.timestamp)} | |
${isCurrentUser ? getMessageStatusIcon(msg) : ''} | |
</div> | |
</div> | |
</div> | |
`; | |
} | |
function getMessageStatusIcon(message) { | |
// FIXED: Proper blue tick logic | |
// Blue ticks ONLY when recipient is ONLINE AND has actually seen the message | |
// Gray ticks when delivered but not seen OR when recipient is offline | |
const seenCount = message.seen_by ? message.seen_by.length : 0; | |
const currentUserId = document.querySelector('[data-user-id]')?.getAttribute('data-user-id'); | |
// For group chats, check if any non-sender has seen it while online | |
const conversation = conversations.find(c => c.id === window.currentConversation); | |
if (!conversation) { | |
// Default to single gray tick if can't find conversation | |
return '<span class="message-status sent"><i class="fas fa-check"></i></span>'; | |
} | |
// Check if ANY recipient is currently online AND has seen the message | |
const hasOnlineRecipientSeen = conversation.participants.some(participant => { | |
return participant.id !== currentUserId && // Not the sender | |
participant.online === true && // Currently online | |
message.seen_by && message.seen_by.includes(participant.id); // Has seen the message | |
}); | |
if (hasOnlineRecipientSeen) { | |
// Blue double tick: seen by online recipient | |
return '<span class="message-status seen"><i class="fas fa-check-double"></i></span>'; | |
} else if (seenCount > 0) { | |
// Gray double tick: seen but recipient was offline or is offline now | |
return '<span class="message-status delivered"><i class="fas fa-check-double"></i></span>'; | |
} else { | |
// Single gray tick: delivered but not seen | |
return '<span class="message-status sent"><i class="fas fa-check"></i></span>'; | |
} | |
} | |
function updateChatHeader(conversationId) { | |
const chatHeader = document.getElementById('chatHeader'); | |
const conversation = conversations.find(c => c.id === conversationId); | |
if (!chatHeader || !conversation) return; | |
chatHeader.innerHTML = ` | |
<div class="d-flex align-items-center"> | |
<div class="avatar bg-success me-3 position-relative"> | |
${conversation.type === 'group' ? '<i class="fas fa-users"></i>' : conversation.name[0].toUpperCase()} | |
${conversation.type === 'private' && conversation.online ? '<div class="online-indicator"></div>' : ''} | |
</div> | |
<div> | |
<div class="fw-bold">${MainJS.escapeHtml(conversation.name)}</div> | |
<small> | |
${conversation.type === 'group' | |
? `${conversation.participants.length} members` | |
: conversation.online ? 'online' : 'offline' | |
} | |
</small> | |
</div> | |
</div> | |
`; | |
} | |
async function handleSendMessage(event) { | |
event.preventDefault(); | |
const messageInput = document.getElementById('messageInput'); | |
const content = messageInput.value.trim(); | |
if (!content || !window.currentConversation) { | |
return; | |
} | |
try { | |
const response = await MainJS.apiRequest('/api/send_message', { | |
method: 'POST', | |
body: JSON.stringify({ | |
conversation_id: window.currentConversation, | |
content: content | |
}) | |
}); | |
if (response.success) { | |
messageInput.value = ''; | |
// Add message to local state for instant display (zero delay like your reference code) | |
if (!messages[window.currentConversation]) { | |
messages[window.currentConversation] = []; | |
} | |
messages[window.currentConversation].push(response.message); | |
// Save messages to local storage | |
saveMessagesToLocalStorage(window.currentConversation, messages[window.currentConversation]); | |
// Render messages instantly for fast response - no setTimeout delays | |
requestAnimationFrame(() => { | |
renderMessages(window.currentConversation); | |
}); | |
// Update conversations list | |
await loadConversations(); | |
} else { | |
MainJS.showError('Failed to send message: ' + response.message); | |
} | |
} catch (error) { | |
console.error('Error sending message:', error); | |
MainJS.showError('Failed to send message'); | |
} | |
} | |
// File download function | |
async function downloadFile(messageId) { | |
try { | |
window.open(`/api/download/${messageId}`, '_blank'); | |
} catch (error) { | |
console.error('Error downloading file:', error); | |
MainJS.showError('Failed to download file'); | |
} | |
} | |
// Audio playback function | |
async function playAudioMessage(messageId) { | |
try { | |
const response = await fetch(`/api/download/${messageId}`); | |
if (response.ok) { | |
const blob = await response.blob(); | |
const audioUrl = URL.createObjectURL(blob); | |
const audio = new Audio(audioUrl); | |
audio.play().catch(error => { | |
console.error('Error playing audio:', error); | |
MainJS.showError('Failed to play audio'); | |
}); | |
// Clean up URL when audio ends | |
audio.addEventListener('ended', () => { | |
URL.revokeObjectURL(audioUrl); | |
}); | |
} | |
} catch (error) { | |
console.error('Error playing audio:', error); | |
MainJS.showError('Failed to play audio'); | |
} | |
} | |
// Helper functions | |
function getCurrentUserId() { | |
return window.currentUserId; | |
} | |
function getConversationType(conversationId) { | |
const conversation = conversations.find(c => c.id === conversationId); | |
return conversation ? conversation.type : 'private'; | |
} | |
async function markMessagesAsSeen(conversationId) { | |
try { | |
// Get all message IDs from current conversation | |
const conversationMessages = messages[conversationId] || []; | |
const messageIds = conversationMessages.map(msg => msg.id); | |
if (messageIds.length > 0) { | |
await MainJS.apiRequest('/api/mark_seen', { | |
method: 'POST', | |
body: JSON.stringify({ | |
message_ids: messageIds | |
}) | |
}); | |
} | |
} catch (error) { | |
console.error('Failed to mark messages as seen:', error); | |
} | |
} | |
function startPolling() { | |
// Poll for new messages every 1 second for instant response like your original code | |
pollingInterval = setInterval(async () => { | |
try { | |
// Reload conversations to get latest messages | |
await loadConversations(); | |
// If a conversation is selected, reload its messages | |
if (window.currentConversation) { | |
await loadMessages(window.currentConversation); | |
markMessagesAsSeen(window.currentConversation); | |
} | |
} catch (error) { | |
console.error('Polling error:', error); | |
} | |
}, 1000); | |
} | |
// New chat functions | |
function startPrivateChat() { | |
const newChatModal = bootstrap.Modal.getInstance(document.getElementById('newChatModal')); | |
const privateChatModal = new bootstrap.Modal(document.getElementById('privateChatModal')); | |
newChatModal.hide(); | |
privateChatModal.show(); | |
} | |
function startGroupChat() { | |
const newChatModal = bootstrap.Modal.getInstance(document.getElementById('newChatModal')); | |
const groupChatModal = new bootstrap.Modal(document.getElementById('groupChatModal')); | |
newChatModal.hide(); | |
groupChatModal.show(); | |
} | |
async function findUser() { | |
const userIdInput = document.getElementById('userIdInput'); | |
const uniqueId = userIdInput.value.trim().toUpperCase(); | |
if (!uniqueId) { | |
MainJS.showError('Please enter a user ID'); | |
return; | |
} | |
try { | |
const response = await MainJS.apiRequest('/api/find_user', { | |
method: 'POST', | |
body: JSON.stringify({ unique_id: uniqueId }) | |
}); | |
const userPreview = document.getElementById('userPreview'); | |
const startChatBtn = document.getElementById('startChatBtn'); | |
if (response.success) { | |
userPreview.innerHTML = ` | |
<div class="d-flex align-items-center"> | |
<div class="avatar bg-success me-3">${response.user.name[0].toUpperCase()}</div> | |
<div> | |
<div class="fw-bold">${MainJS.escapeHtml(response.user.name)}</div> | |
<small class="text-muted">${response.user.unique_id}</small> | |
</div> | |
</div> | |
`; | |
userPreview.style.display = 'block'; | |
startChatBtn.style.display = 'block'; | |
startChatBtn.dataset.userId = response.user.user_id; | |
} else { | |
userPreview.innerHTML = `<div class="text-danger">${response.message}</div>`; | |
userPreview.style.display = 'block'; | |
startChatBtn.style.display = 'none'; | |
} | |
} catch (error) { | |
console.error('Error finding user:', error); | |
MainJS.showError('Failed to find user'); | |
} | |
} | |
async function handleStartPrivateChat(event) { | |
event.preventDefault(); | |
const startChatBtn = document.getElementById('startChatBtn'); | |
const userId = startChatBtn.dataset.userId; | |
if (!userId) { | |
MainJS.showError('Please find a user first'); | |
return; | |
} | |
try { | |
const response = await MainJS.apiRequest('/api/start_private_chat', { | |
method: 'POST', | |
body: JSON.stringify({ user_id: userId }) | |
}); | |
if (response.success) { | |
const privateChatModal = bootstrap.Modal.getInstance(document.getElementById('privateChatModal')); | |
privateChatModal.hide(); | |
// Refresh conversations and select the new one | |
await loadConversations(); | |
await selectConversation(response.conversation_id); | |
} else { | |
MainJS.showError('Failed to start chat: ' + response.message); | |
} | |
} catch (error) { | |
console.error('Error starting private chat:', error); | |
MainJS.showError('Failed to start chat'); | |
} | |
} | |
async function handleCreateGroup(event) { | |
event.preventDefault(); | |
const groupName = document.getElementById('groupNameInput').value.trim(); | |
const memberInputs = document.querySelectorAll('.member-input'); | |
const members = Array.from(memberInputs) | |
.map(input => input.value.trim().toUpperCase()) | |
.filter(value => value); | |
if (!groupName) { | |
MainJS.showError('Please enter a group name'); | |
return; | |
} | |
if (members.length === 0) { | |
MainJS.showError('Please add at least one member'); | |
return; | |
} | |
try { | |
const response = await MainJS.apiRequest('/api/create_group', { | |
method: 'POST', | |
body: JSON.stringify({ | |
name: groupName, | |
members: members | |
}) | |
}); | |
if (response.success) { | |
const groupChatModal = bootstrap.Modal.getInstance(document.getElementById('groupChatModal')); | |
groupChatModal.hide(); | |
// Reset form | |
document.getElementById('groupChatForm').reset(); | |
// Refresh conversations and select the new one | |
await loadConversations(); | |
await selectConversation(response.conversation_id); | |
} else { | |
MainJS.showError('Failed to create group: ' + response.message); | |
} | |
} catch (error) { | |
console.error('Error creating group:', error); | |
MainJS.showError('Failed to create group'); | |
} | |
} | |
function addMemberField() { | |
const groupMembers = document.getElementById('groupMembers'); | |
const memberCount = groupMembers.querySelectorAll('.member-input').length; | |
if (memberCount >= 9) { | |
MainJS.showError('Maximum 9 members allowed'); | |
return; | |
} | |
const memberField = document.createElement('div'); | |
memberField.className = 'input-group mb-2'; | |
memberField.innerHTML = ` | |
<input type="text" class="form-control member-input" placeholder="Enter user ID"> | |
<button type="button" class="btn btn-outline-danger" onclick="removeMemberField(this)"> | |
<i class="fas fa-minus"></i> | |
</button> | |
`; | |
groupMembers.appendChild(memberField); | |
} | |
function removeMemberField(button) { | |
button.parentElement.remove(); | |
} | |
// Image preview functionality (WhatsApp-like) - SIMPLIFIED TO PREVENT TOUCH ISSUES | |
function openImagePreview(messageId) { | |
// Remove any existing image preview first | |
const existingModal = document.querySelector('.image-preview-modal'); | |
if (existingModal) { | |
existingModal.remove(); | |
} | |
const imageUrl = `/api/image/${messageId}`; | |
// Create simple modal with minimal interference | |
const modal = document.createElement('div'); | |
modal.className = 'image-preview-modal'; | |
modal.innerHTML = ` | |
<div class="image-preview-overlay"> | |
<div class="image-preview-container"> | |
<button class="image-preview-close" onclick="closeImagePreview()"> | |
<i class="fas fa-times"></i> | |
</button> | |
<img src="${imageUrl}" alt="Image Preview" class="image-preview-image"> | |
<div class="image-preview-actions"> | |
<a href="/api/download/${messageId}" target="_blank" class="btn btn-outline-light"> | |
<i class="fas fa-download"></i> Download | |
</a> | |
</div> | |
</div> | |
</div> | |
`; | |
// Simple modal without any complex touch handling | |
document.body.appendChild(modal); | |
// Add click to close functionality | |
modal.querySelector('.image-preview-overlay').addEventListener('click', (e) => { | |
if (e.target === e.currentTarget) { | |
closeImagePreview(); | |
} | |
}); | |
// Mark message as seen when viewing image | |
markMessageAsSeen(messageId); | |
} | |
function closeImagePreview() { | |
// Simple close function - just remove modal, no complex touch handling | |
const modal = document.querySelector('.image-preview-modal'); | |
if (modal) { | |
modal.remove(); | |
console.log('Image preview closed'); | |
} | |
} | |
// Touch event prevention function for modal | |
function preventTouch(e) { | |
e.preventDefault(); | |
} | |
// Double blue tick system - Mark messages as seen | |
async function markMessageAsSeen(messageId) { | |
try { | |
await MainJS.apiRequest('/api/mark_seen', { | |
method: 'POST', | |
body: JSON.stringify({ message_ids: [messageId] }) | |
}); | |
} catch (error) { | |
console.error('Error marking message as seen:', error); | |
} | |
} | |
// Mark all visible messages as seen | |
async function markVisibleMessagesAsSeen() { | |
if (!window.currentConversation) return; | |
const conversationMessages = messages[window.currentConversation] || []; | |
const messageIds = conversationMessages | |
.filter(msg => msg.sender_id !== getCurrentUserId()) // Only mark messages from others | |
.map(msg => msg.id); | |
if (messageIds.length > 0) { | |
try { | |
await MainJS.apiRequest('/api/mark_seen', { | |
method: 'POST', | |
body: JSON.stringify({ message_ids: messageIds }) | |
}); | |
} catch (error) { | |
console.error('Error marking messages as seen:', error); | |
} | |
} | |
} | |
// Update message status (for double blue tick display) | |
async function updateMessageStatuses() { | |
if (!window.currentConversation) return; | |
const conversationMessages = messages[window.currentConversation] || []; | |
const currentUserId = getCurrentUserId(); | |
// Only check status for messages sent by current user | |
const sentMessages = conversationMessages.filter(msg => msg.sender_id === currentUserId); | |
for (const message of sentMessages) { | |
try { | |
const response = await MainJS.apiRequest(`/api/message_status/${message.id}`); | |
if (response.success) { | |
message.status = response.status; | |
} | |
} catch (error) { | |
console.error('Error updating message status:', error); | |
} | |
} | |
} | |
// Local Storage Functions | |
function saveMessagesToLocalStorage(conversationId, messageList) { | |
try { | |
const storageKey = `chat_messages_${conversationId}`; | |
localStorage.setItem(storageKey, JSON.stringify(messageList)); | |
console.log(`Saved ${messageList.length} messages to local storage for conversation ${conversationId}`); | |
} catch (error) { | |
console.error('Failed to save messages to local storage:', error); | |
} | |
} | |
function loadMessagesFromLocalStorage(conversationId) { | |
try { | |
const storageKey = `chat_messages_${conversationId}`; | |
const stored = localStorage.getItem(storageKey); | |
const messages = stored ? JSON.parse(stored) : []; | |
console.log(`Loaded ${messages.length} messages from local storage for conversation ${conversationId}`); | |
return messages; | |
} catch (error) { | |
console.error('Failed to load messages from local storage:', error); | |
return []; | |
} | |
} | |
function clearLocalStorageForConversation(conversationId) { | |
try { | |
const storageKey = `chat_messages_${conversationId}`; | |
localStorage.removeItem(storageKey); | |
} catch (error) { | |
console.error('Failed to clear local storage:', error); | |
} | |
} | |
// Load messages from local storage first, then update from server | |
async function loadMessagesWithLocalStorage(conversationId) { | |
try { | |
console.log('Loading messages with local storage for:', conversationId); | |
// First show cached messages for instant loading | |
const cachedMessages = loadMessagesFromLocalStorage(conversationId); | |
console.log('Cached messages loaded:', cachedMessages.length); | |
if (cachedMessages.length > 0) { | |
messages[conversationId] = cachedMessages; | |
renderMessages(conversationId); | |
console.log('Rendered cached messages'); | |
} | |
// Then load fresh messages from server | |
console.log('Loading fresh messages from server...'); | |
await loadMessages(conversationId); | |
} catch (error) { | |
console.error('Error in loadMessagesWithLocalStorage:', error); | |
// Fallback to regular loading | |
await loadMessages(conversationId); | |
} | |
} | |
// Mobile sidebar functions | |
function toggleMobileSidebar() { | |
const sidebar = document.getElementById('sidebar'); | |
const overlay = document.getElementById('sidebarOverlay'); | |
if (sidebar && overlay) { | |
sidebar.classList.toggle('show'); | |
overlay.classList.toggle('show'); | |
} | |
} | |
function closeMobileSidebar() { | |
const sidebar = document.getElementById('sidebar'); | |
const overlay = document.getElementById('sidebarOverlay'); | |
if (sidebar && overlay) { | |
sidebar.classList.remove('show'); | |
overlay.classList.remove('show'); | |
} | |
} | |
// Chat creation functions | |
function startPrivateChat() { | |
// Close new chat modal and open private chat modal | |
const newChatModal = bootstrap.Modal.getInstance(document.getElementById('newChatModal')); | |
const privateChatModal = new bootstrap.Modal(document.getElementById('privateChatModal')); | |
if (newChatModal) newChatModal.hide(); | |
privateChatModal.show(); | |
} | |
function startGroupChat() { | |
// Close new chat modal and open group chat modal | |
const newChatModal = bootstrap.Modal.getInstance(document.getElementById('newChatModal')); | |
const groupChatModal = new bootstrap.Modal(document.getElementById('groupChatModal')); | |
if (newChatModal) newChatModal.hide(); | |
groupChatModal.show(); | |
} | |
async function findUser() { | |
const userIdInput = document.getElementById('userIdInput'); | |
const userPreview = document.getElementById('userPreview'); | |
const startChatBtn = document.getElementById('startChatBtn'); | |
const userId = userIdInput.value.trim(); | |
if (!userId) { | |
MainJS.showError('Please enter a user ID'); | |
return; | |
} | |
try { | |
const response = await MainJS.apiRequest('/api/find-user', { | |
method: 'POST', | |
body: JSON.stringify({ user_id: userId }) | |
}); | |
if (response.success && response.user) { | |
userPreview.innerHTML = ` | |
<div class="d-flex align-items-center"> | |
<div class="avatar bg-success me-3"> | |
${response.user.name[0].toUpperCase()} | |
</div> | |
<div> | |
<div class="fw-bold">${MainJS.escapeHtml(response.user.name)}</div> | |
<small class="text-muted">${MainJS.escapeHtml(response.user.email || 'No email')}</small> | |
</div> | |
</div> | |
`; | |
userPreview.style.display = 'block'; | |
startChatBtn.style.display = 'block'; | |
window.foundUser = response.user; | |
} else { | |
MainJS.showError('User not found'); | |
userPreview.style.display = 'none'; | |
startChatBtn.style.display = 'none'; | |
} | |
} catch (error) { | |
console.error('Find user error:', error); | |
MainJS.showError('Failed to find user'); | |
} | |
} | |
// Cleanup on page unload | |
window.addEventListener('beforeunload', () => { | |
if (pollingInterval) { | |
clearInterval(pollingInterval); | |
} | |
}); | |