// Function to delete message async function deleteMessage(messageId, forEveryone) { try { const response = await fetch("/api/delete-message", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${currentToken}`, }, body: JSON.stringify({ message_id: messageId, for_everyone: forEveryone, }), }); const data = await response.json(); if (data.success) { console.log(`✅ Message deleted: ${data.message}`); showAlert(data.message, "success"); // Remove message from UI const messageElement = document.querySelector( `[data-message-id="${messageId}"]`, ); if (messageElement) { messageElement.remove(); } // Update chat history if (messageHistory[currentRecipient]) { messageHistory[currentRecipient] = messageHistory[ currentRecipient ].filter((msg) => msg.id !== messageId); } // Reload chats to update last message loadChats(); } else { console.error("❌ Failed to delete message:", data.message); showAlert(data.message, "error"); } } catch (error) { console.error("❌ Error deleting message:", error); showAlert("Error deleting message", "error"); } } // Function to mark messages as read async function markMessagesAsRead(fromUser) { try { const response = await fetch("/api/mark-read", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${currentToken}`, }, body: JSON.stringify({ from_user: fromUser, to_user: currentUser, }), }); const data = await response.json(); if (data.success) { console.log(`✅ Marked messages as read from ${fromUser}`); // Обновляем список чатов чтобы убрать красный badge loadChats(); } else { console.error("❌ Failed to mark messages as read:", data.error); } } catch (error) { console.error("❌ Error marking messages as read:", error); } } function deleteMessageForMe() { if (messageToDelete) { deleteMessage(messageToDelete, false); closeDeleteModal(); } } function deleteMessageForEveryone() { if (messageToDelete) { deleteMessage(messageToDelete, true); closeDeleteModal(); } } function handleChatMessage(msg) { console.log("Handling chat message:", msg); const otherUser = msg.from_user === currentUser ? msg.to_user : msg.from_user; if (!messageHistory[otherUser]) { messageHistory[otherUser] = []; } const messageExists = messageHistory[otherUser].some( (existingMsg) => existingMsg.id === msg.id, ); if (!messageExists) { messageHistory[otherUser].push(msg); } loadChats(); if (otherUser === currentRecipient) { displayMessage(msg, true); // Если сообщение пришло в открытый чат, сразу помечаем как прочитанное if (msg.from_user === currentRecipient) { markMessagesAsRead(currentRecipient); } } } function displayMessage(msg, scrollToBottom) { const chatMessages = document.getElementById("chatMessages"); // Remove empty state if it exists const emptyState = chatMessages.querySelector(".empty-state"); if (emptyState) { emptyState.remove(); } const messageDiv = document.createElement("div"); messageDiv.className = `message ${msg.from_user === currentUser ? "outgoing" : "incoming"}`; messageDiv.setAttribute("data-message-id", msg.id); if (msg.message_type === "system") { messageDiv.className = "system-message"; messageDiv.textContent = msg.content; } else { const isOutgoing = msg.from_user === currentUser; if (!isOutgoing) { const senderDiv = document.createElement("div"); senderDiv.className = "message-sender"; senderDiv.textContent = msg.from_user; messageDiv.appendChild(senderDiv); } const contentDiv = document.createElement("div"); contentDiv.className = "message-content"; // Handle different message types if (msg.message_type === "text") { contentDiv.textContent = msg.content; } else if (msg.message_type === "file") { const fileInfo = JSON.parse(msg.content); contentDiv.innerHTML = `
📄
${fileInfo.filename}
${formatFileSize(fileInfo.size)}
${fileInfo.mime_type}
⬇️ Download
`; } else if (msg.message_type === "photo") { const photoInfo = JSON.parse(msg.content); contentDiv.innerHTML = `
Photo
${photoInfo.filename}
${formatFileSize(photoInfo.size)}
⬇️ Download
`; } else if (msg.message_type === "video") { const videoInfo = JSON.parse(msg.content); contentDiv.innerHTML = `
${videoInfo.filename}
${formatFileSize(videoInfo.size)}
⬇️ Download
`; } else if (msg.message_type === "audio") { const audioInfo = JSON.parse(msg.content); contentDiv.innerHTML = `
${audioInfo.filename}
${formatFileSize(audioInfo.size)}
⬇️ Download
`; } messageDiv.appendChild(contentDiv); const timeDiv = document.createElement("div"); timeDiv.className = "message-time"; const date = new Date(msg.timestamp); timeDiv.textContent = date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", }); messageDiv.appendChild(timeDiv); // Add delete button for user's own messages or all messages if (msg.from_user === currentUser) { const actionsDiv = document.createElement("div"); actionsDiv.className = "message-actions"; const deleteBtn = document.createElement("button"); deleteBtn.className = "delete-btn"; deleteBtn.innerHTML = "×"; deleteBtn.title = "Delete message"; deleteBtn.onclick = (e) => { console.log("delete message button clicked"); e.stopPropagation(); showDeleteModal(msg.id); }; actionsDiv.appendChild(deleteBtn); messageDiv.appendChild(actionsDiv); } } chatMessages.appendChild(messageDiv); console.log("message added"); if (scrollToBottom) { setTimeout(() => { chatMessages.scrollTop = chatMessages.scrollHeight; }, 100); } } function formatFileSize(bytes) { if (bytes === 0) return "0 Bytes"; const k = 1024; const sizes = ["Bytes", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; } function viewImage(imageUrl) { // Create a modal to view the image const modal = document.createElement("div"); modal.className = "image-viewer-modal"; modal.innerHTML = `
× Full size image
`; document.body.appendChild(modal); // Close modal when clicking on close button modal.querySelector(".close-image-viewer").onclick = function () { document.body.removeChild(modal); }; // Close modal when clicking outside the image modal.onclick = function (event) { if (event.target === modal) { document.body.removeChild(modal); } }; } function addSystemMessage(content) { const chatMessages = document.getElementById("chatMessages"); // Remove empty state if it exists const emptyState = chatMessages.querySelector(".empty-state"); if (emptyState) { emptyState.remove(); } const systemDiv = document.createElement("div"); systemDiv.className = "system-message"; systemDiv.textContent = content; chatMessages.appendChild(systemDiv); setTimeout(() => { chatMessages.scrollTop = chatMessages.scrollHeight; }, 100); } function sendMessage() { if (!ws || ws.readyState !== WebSocket.OPEN) { showAlert("Please connect to the server first", "error"); return; } if (!currentRecipient) { showAlert("Please select a user to chat with", "error"); return; } const input = document.getElementById("messageInput"); const content = input.value.trim(); if (!content) return; const message = { id: generateId(), from_user: currentUser, to_user: currentRecipient, content: content, timestamp: Date.now(), message_type: "text", }; console.log("Sending message:", message); ws.send(JSON.stringify(message)); input.value = ""; input.style.height = "auto"; setTimeout(() => loadChats(), 100); } // Queue to handle file uploads sequentially let uploadQueue = []; let isUploading = false; // Function to process the upload queue async function processUploadQueue() { if (isUploading || uploadQueue.length === 0) { return; } isUploading = true; const file = uploadQueue.shift(); // Get the first file in the queue try { await sendFileMessageInternal(file); } catch (error) { console.error("Upload failed for file:", error); } finally { isUploading = false; // Process next file in queue if available setTimeout(processUploadQueue, 100); // Small delay to allow server to settle } } // Internal function to handle the actual file upload async function sendFileMessageInternal(file) { if (!ws || ws.readyState !== WebSocket.OPEN) { showAlert("Please connect to the server first", "error"); return; } if (!currentRecipient) { showAlert("Please select a user to chat with", "error"); return; } // Show upload progress const chatMessages = document.getElementById("chatMessages"); const progressDiv = document.createElement("div"); progressDiv.className = "upload-progress"; progressDiv.innerHTML = `
Uploading ${file.name}
`; chatMessages.appendChild(progressDiv); try { // Create FormData for file upload const formData = new FormData(); formData.append("file", file); // Upload file to server with better error handling const response = await fetch("/api/upload", { method: "POST", headers: { Authorization: `Bearer ${currentToken}`, }, body: formData, }).catch((error) => { console.error("Network error during upload:", error); throw new Error("Network error during upload. Please try again."); }); // Remove progress indicator progressDiv.remove(); if (!response.ok) { throw new Error(`Upload failed: ${response.status}`); } const result = await response.json(); if (!result.success) { throw new Error(result.error || "Upload failed"); } // Create message object based on file type let message; let messageType; if (result.file_type === "photo") { messageType = "photo"; message = { id: generateId(), from_user: currentUser, to_user: currentRecipient, content: JSON.stringify(result.info), timestamp: Date.now(), message_type: messageType, }; } else if (result.file_type === "video") { messageType = "video"; message = { id: generateId(), from_user: currentUser, to_user: currentRecipient, content: JSON.stringify(result.info), timestamp: Date.now(), message_type: messageType, }; } else if (result.file_type === "audio") { messageType = "audio"; message = { id: generateId(), from_user: currentUser, to_user: currentRecipient, content: JSON.stringify(result.info), timestamp: Date.now(), message_type: messageType, }; } else { messageType = "file"; message = { id: generateId(), from_user: currentUser, to_user: currentRecipient, content: JSON.stringify(result.info), timestamp: Date.now(), message_type: messageType, }; } console.log("Sending file message:", message); ws.send(JSON.stringify(message)); setTimeout(() => loadChats(), 100); } catch (error) { // Remove progress indicator progressDiv.remove(); console.error("Upload error:", error); showAlert(`Failed to upload file: ${error.message}`, "error"); } } // Function to send a file message (adds to queue) async function sendFileMessage(file) { // Check if file is already in queue to prevent duplicates const fileName = file.name; const fileSize = file.size; const isInQueue = uploadQueue.some( (item) => item.name === fileName && item.size === fileSize, ); if (isInQueue) { console.log( `File ${fileName} is already in upload queue, skipping duplicate.`, ); return; } // Add file to upload queue uploadQueue.push(file); console.log( `File ${file.name} added to upload queue. Queue length: ${uploadQueue.length}`, ); // Process the queue setTimeout(processUploadQueue, 0); } // Event listeners document .getElementById("messageInput") .addEventListener("keydown", function (e) { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); // Track if event listener is already attached to prevent duplicates if (!window.fileInputListenerAttached) { // File input event listener document.getElementById("fileInput").addEventListener("change", function (e) { const file = e.target.files[0]; if (file) { // Add file to upload queue regardless of current upload status // The queue system will handle the sequencing uploadQueue.push(file); console.log( `File ${file.name} added to upload queue. Queue length: ${uploadQueue.length}`, ); // Process the queue setTimeout(processUploadQueue, 0); // Reset the input so the same file can be selected again e.target.value = ""; } }); window.fileInputListenerAttached = true; }