// 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 = `
`;
} else if (msg.message_type === "video") {
const videoInfo = JSON.parse(msg.content);
contentDiv.innerHTML = `
`;
} else if (msg.message_type === "audio") {
const audioInfo = JSON.parse(msg.content);
contentDiv.innerHTML = `
`;
}
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 = `
×
`;
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;
}