Files
msgs/web/js/messages.js
T
wt 9016889a78
Continuous integration / Check (push) Failing after 10s
Continuous integration / Rustfmt (push) Successful in 36s
Continuous integration / build (push) Successful in 36s
Continuous integration / build-release (push) Successful in 57s
added file audio video and files
2026-02-15 16:55:51 +07:00

521 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 = `
<div class="file-message">
<div class="file-icon">📄</div>
<div class="file-info">
<div class="file-name">${fileInfo.filename}</div>
<div class="file-size">${formatFileSize(fileInfo.size)}</div>
<div class="file-type">${fileInfo.mime_type}</div>
</div>
<a href="${fileInfo.filepath}" download="${fileInfo.filename}" class="download-btn">⬇️ Download</a>
</div>
`;
} else if (msg.message_type === "photo") {
const photoInfo = JSON.parse(msg.content);
contentDiv.innerHTML = `
<div class="media-message">
<img src="${photoInfo.filepath}" alt="Photo" class="message-image" onclick="viewImage('${photoInfo.filepath}')"/>
<div class="file-info">
<div class="file-name">${photoInfo.filename}</div>
<div class="file-size">${formatFileSize(photoInfo.size)}</div>
</div>
<a href="${photoInfo.filepath}" download="${photoInfo.filename}" class="download-btn">⬇️ Download</a>
</div>
`;
} else if (msg.message_type === "video") {
const videoInfo = JSON.parse(msg.content);
contentDiv.innerHTML = `
<div class="media-message">
<video controls class="message-video">
<source src="${videoInfo.filepath}" type="${videoInfo.mime_type}">
Your browser does not support the video tag.
</video>
<div class="file-info">
<div class="file-name">${videoInfo.filename}</div>
<div class="file-size">${formatFileSize(videoInfo.size)}</div>
</div>
<a href="${videoInfo.filepath}" download="${videoInfo.filename}" class="download-btn">⬇️ Download</a>
</div>
`;
} else if (msg.message_type === "audio") {
const audioInfo = JSON.parse(msg.content);
contentDiv.innerHTML = `
<div class="media-message">
<audio controls class="message-audio">
<source src="${audioInfo.filepath}" type="${audioInfo.mime_type}">
Your browser does not support the audio tag.
</audio>
<div class="file-info">
<div class="file-name">${audioInfo.filename}</div>
<div class="file-size">${formatFileSize(audioInfo.size)}</div>
</div>
<a href="${audioInfo.filepath}" download="${audioInfo.filename}" class="download-btn">⬇️ Download</a>
</div>
`;
}
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 = `
<div class="image-viewer-content">
<span class="close-image-viewer">&times;</span>
<img src="${imageUrl}" alt="Full size image" class="image-viewer-large">
</div>
`;
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 = `<div class="progress-text">Uploading ${file.name}</div><div class="progress-bar"><div class="progress-fill"></div></div>`;
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;
}