439 lines
22 KiB
JavaScript
439 lines
22 KiB
JavaScript
document.addEventListener('DOMContentLoaded', (event) => {
|
|
const socket = io();
|
|
const md = window.markdownit();
|
|
|
|
function requestNotificationPermission() {
|
|
if (Notification.permission !== "granted") {
|
|
Notification.requestPermission();
|
|
}
|
|
}
|
|
|
|
function showNotification(title, body) {
|
|
if (Notification.permission === "granted") {
|
|
new Notification(title, { body });
|
|
}
|
|
}
|
|
|
|
requestNotificationPermission();
|
|
|
|
socket.emit('join', { username: username });
|
|
|
|
document.title = `${friendUsername}`;
|
|
|
|
const messagesList = document.getElementById('messages');
|
|
const imageOverlay = document.getElementById('image-overlay');
|
|
const overlayImage = document.getElementById('overlay-image');
|
|
const contextMenu = document.getElementById('context-menu');
|
|
const toast = document.getElementById('toast');
|
|
const toastMessage = document.getElementById('toast-message');
|
|
let currentMessageId = null;
|
|
let currentMessageText = null;
|
|
|
|
function showToast(message) {
|
|
toastMessage.textContent = message;
|
|
toast.classList.add('show');
|
|
setTimeout(() => {
|
|
toast.classList.remove('show');
|
|
}, 3000);
|
|
}
|
|
|
|
function scrollToBottom() {
|
|
messagesList.scrollTop = messagesList.scrollHeight;
|
|
}
|
|
|
|
function shouldShowUsername(previousTimestamp, currentTimestamp) {
|
|
const tenMinutes = 10 * 60 * 1000;
|
|
return (currentTimestamp - previousTimestamp) > tenMinutes;
|
|
}
|
|
|
|
function formatLocalTime(utcTimestamp) {
|
|
const date = new Date(utcTimestamp);
|
|
const localDate = new Date(date.getTime() - date.getTimezoneOffset() * 60000);
|
|
|
|
const now = new Date();
|
|
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
|
|
|
|
const timeFormatter = { hour: '2-digit', minute: '2-digit' };
|
|
const dateFormatter = { year: 'numeric', month: '2-digit', day: '2-digit' };
|
|
|
|
let formattedTime;
|
|
if (localDate >= today) {
|
|
formattedTime = `Today at ${localDate.toLocaleTimeString(undefined, timeFormatter)}`;
|
|
} else if (localDate >= yesterday) {
|
|
formattedTime = `Yesterday at ${localDate.toLocaleTimeString(undefined, timeFormatter)}`;
|
|
} else {
|
|
formattedTime = `${localDate.toLocaleDateString(undefined, dateFormatter)} at ${localDate.toLocaleTimeString(undefined, timeFormatter)}`;
|
|
}
|
|
return formattedTime;
|
|
}
|
|
|
|
function isImageUrl(url) {
|
|
return /\.(jpg|jpeg|png|gif|bmp|svg)$/i.test(url);
|
|
}
|
|
|
|
function appendMessage(msg, showUsername, senderProfilePicture) {
|
|
const currentTimestamp = new Date(msg.timestamp).getTime();
|
|
previousSender = msg.sender;
|
|
previousTimestamp = currentTimestamp;
|
|
|
|
const newMessage = document.createElement('div');
|
|
newMessage.classList.add('message');
|
|
|
|
if (showUsername) {
|
|
const usernameElement = document.createElement('div');
|
|
usernameElement.classList.add('message-sender');
|
|
if (msg.sender === username) {
|
|
usernameElement.classList.add('message-myself-sender');
|
|
}
|
|
usernameElement.innerHTML = `
|
|
<img src="/cdn/${senderProfilePicture}" alt="/cdn/${msg.sender}" class="profile-picture">
|
|
<strong>${msg.sender}</strong>
|
|
<div class="message-sender-timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
|
messagesList.appendChild(usernameElement);
|
|
}
|
|
|
|
if (msg.content_type === 'text') {
|
|
if (isImageUrl(msg.content)) {
|
|
newMessage.innerHTML = `<img src="${msg.content}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
|
} else {
|
|
newMessage.innerHTML = `${md.render(msg.content)}<div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
|
}
|
|
} else if (msg.content_type === 'file') {
|
|
const isImage = isImageUrl(msg.content);
|
|
const isVideo = /\.(mp4|webm|ogg)$/i.test(msg.content);
|
|
if (isImage) {
|
|
newMessage.innerHTML = `<img src="/cdn/${msg.content}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
|
} else if (isVideo) {
|
|
newMessage.innerHTML = `<video controls style="max-width: 200px; max-height: 200px;"><source src="/cdn/${msg.content}" type="video/mp4"></video><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
|
} else {
|
|
newMessage.innerHTML = `<a href="/cdn/${msg.content}" target="_blank">Download File</a><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
|
}
|
|
}
|
|
|
|
messagesList.appendChild(newMessage);
|
|
scrollToBottom();
|
|
}
|
|
|
|
let previousSender = null;
|
|
let previousTimestamp = 0;
|
|
|
|
socket.on('new_message', function(data) {
|
|
// Check if the message is for the current chat
|
|
if (friendUsername !== data.sender && friendId != data.sender_id) {
|
|
return;
|
|
}
|
|
|
|
showNotification('New Message', `You have received a new message from ${data.sender}`);
|
|
const currentTimestamp = new Date(data.timestamp).getTime();
|
|
const showUsername = previousSender !== data.sender || shouldShowUsername(previousTimestamp, currentTimestamp);
|
|
previousSender = data.sender;
|
|
previousTimestamp = currentTimestamp;
|
|
|
|
const messageElement = document.createElement('div');
|
|
messageElement.classList.add('message');
|
|
|
|
const newMessage = document.createElement('div');
|
|
newMessage.classList.add('message');
|
|
|
|
newMessage.dataset.messageId = data.id;
|
|
newMessage.dataset.messageText = data.content;
|
|
|
|
if (showUsername) {
|
|
const usernameElement = document.createElement('div');
|
|
usernameElement.classList.add('message-sender');
|
|
const timestampFormatted = formatLocalTime(data.timestamp);
|
|
|
|
// Include the profile picture if available
|
|
const profilePicture = data.sender_profile_picture ? `<img src="/cdn/${data.sender_profile_picture}" alt="${data.sender}" class="profile-picture enhanceable-image">` : '';
|
|
|
|
usernameElement.innerHTML = `${profilePicture} <strong>${data.sender}</strong><div class="message-sender-timestamp">${timestampFormatted}</div>`;
|
|
messagesList.appendChild(usernameElement);
|
|
}
|
|
|
|
if (data.content_type === 'text') {
|
|
if (isImageUrl(data.content)) {
|
|
newMessage.innerHTML = `<img src="${data.content}" alt="Image" class="enhanceable-image" style="max-width: 300px; max-height: 300px;" /><div class="timestamp">${formatLocalTime(data.timestamp)}</div>`;
|
|
} else {
|
|
newMessage.innerHTML = `${md.render(data.content)}<div class="timestamp">${formatLocalTime(data.timestamp)}</div>`;
|
|
}
|
|
} else if (data.content_type === 'file') {
|
|
const isImage = isImageUrl(data.content);
|
|
const isVideo = /\.(mp4|webm|ogg)$/i.test(data.content);
|
|
if (isImage) {
|
|
newMessage.innerHTML = `<img src="/cdn/${data.content}" alt="Image" class="enhanceable-image" style="max-width: 300px; max-height: 300px;" /><div class="timestamp">${formatLocalTime(data.timestamp)}</div>`;
|
|
} else if (isVideo) {
|
|
newMessage.innerHTML = `<video controls style="max-width: 300px; max-height: 300px;"><source src="/cdn/${data.content}" type="video/mp4"></video><div class="timestamp">${formatLocalTime(data.timestamp)}</div>`;
|
|
} else {
|
|
newMessage.innerHTML = `<a href="/cdn/${data.content}" target="_blank">Download File</a><div class="timestamp">${formatLocalTime(data.timestamp)}</div>`;
|
|
}
|
|
}
|
|
|
|
messagesList.appendChild(newMessage);
|
|
scrollToBottom();
|
|
});
|
|
|
|
|
|
if (friendId) {
|
|
fetch(`/get_messages/${friendId}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
messagesList.innerHTML = '';
|
|
document.getElementById('chat-with').textContent = `${friendUsername}`;
|
|
document.getElementById('send-message-form').dataset.receiver = friendId;
|
|
|
|
data.messages.forEach((msg, index, messages) => {
|
|
const currentTimestamp = new Date(msg.timestamp).getTime();
|
|
const previousMessage = messages[index - 1];
|
|
const previousTimestamp = previousMessage ? new Date(previousMessage.timestamp).getTime() : 0;
|
|
const showUsername = index === 0 || msg.sender !== previousMessage.sender || shouldShowUsername(previousTimestamp, currentTimestamp);
|
|
|
|
if (showUsername) {
|
|
const usernameElement = document.createElement('div');
|
|
usernameElement.classList.add('message-sender');
|
|
if (msg.sender === username) {
|
|
usernameElement.classList.add('message-myself-sender');
|
|
}
|
|
// Include the profile picture if available
|
|
const profilePicture = msg.sender_profile_picture ? `<img src="${msg.sender_profile_picture}" alt="${msg.sender}" class="profile-picture enhanceable-image">` : '';
|
|
usernameElement.innerHTML = `${profilePicture} <strong>${msg.sender}</strong><div class="message-sender-timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
|
messagesList.appendChild(usernameElement);
|
|
}
|
|
|
|
const messageElement = document.createElement('div');
|
|
messageElement.classList.add('message');
|
|
|
|
messageElement.dataset.messageId = msg.id;
|
|
messageElement.dataset.messageText = msg.content;
|
|
|
|
if (msg.content_type === 'text') {
|
|
if (isImageUrl(msg.content)) {
|
|
messageElement.innerHTML = `<img src="${msg.content}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
|
} else {
|
|
messageElement.innerHTML = `${md.render(msg.content)}<div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
|
}
|
|
} else if (msg.content_type === 'file') {
|
|
const isImage = isImageUrl(msg.content);
|
|
const isVideo = /\.(mp4|webm|ogg)$/i.test(msg.content);
|
|
if (isImage) {
|
|
messageElement.innerHTML = `<img src="/cdn/${msg.content}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
|
} else if (isVideo) {
|
|
messageElement.innerHTML = `<video controls style="max-width: 200px; max-height: 200px;"><source src="/cdn/${msg.content}" type="video/mp4"></video><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
|
} else {
|
|
messageElement.innerHTML = `<a href="/cdn/${msg.content}" target="_blank">Download File</a><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
|
}
|
|
}
|
|
|
|
messagesList.appendChild(messageElement);
|
|
});
|
|
|
|
scrollToBottom();
|
|
})
|
|
.catch(error => console.error('Error fetching messages:', error));
|
|
}
|
|
|
|
|
|
const sendMessageForm = document.querySelector('.send-message-form');
|
|
if (sendMessageForm) {
|
|
sendMessageForm.addEventListener('submit', function(event) {
|
|
event.preventDefault();
|
|
const receiver = this.dataset.receiver;
|
|
const contentInput = this.querySelector('input[name="content"]');
|
|
const fileInput = this.querySelector('input[name="file"]');
|
|
const content = contentInput.value;
|
|
const timestamp = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
|
const imagePreview = document.querySelector('.file-preview-image');
|
|
const videoPreview = document.querySelector('.file-preview-video');
|
|
const filenamePreview = document.querySelector('.file-preview-filename');
|
|
|
|
if (content || fileInput.files.length > 0) {
|
|
// Append the message locally
|
|
const newMessage = document.createElement('div');
|
|
newMessage.classList.add('message');
|
|
newMessage.dataset.messageId = 'temp-id'; // Temporary ID for the new message
|
|
const currentTimestamp = new Date(timestamp).getTime();
|
|
const showUsername = previousSender !== username || shouldShowUsername(previousTimestamp, currentTimestamp);
|
|
previousSender = username;
|
|
previousTimestamp = currentTimestamp;
|
|
|
|
if (showUsername) {
|
|
const usernameElement = document.createElement('div');
|
|
usernameElement.classList.add('message-sender');
|
|
const timestampFormatted = formatLocalTime(timestamp);
|
|
const profilePictureUrl = `/cdn/${profilePicture}`; // Correct reference
|
|
usernameElement.innerHTML = `<img src="${profilePictureUrl}" alt="${username}" class="profile-picture enhanceable-image"> <strong>${username}</strong><div class="message-sender-timestamp">${timestampFormatted}</div>`;
|
|
messagesList.appendChild(usernameElement);
|
|
}
|
|
|
|
|
|
|
|
if (content) {
|
|
newMessage.dataset.messageText = content;
|
|
if (isImageUrl(content)) {
|
|
newMessage.innerHTML = `<img src="${content}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${formatLocalTime(timestamp)}</div>`;
|
|
} else {
|
|
newMessage.innerHTML = `${md.render(content)}<div class="timestamp">${formatLocalTime(timestamp)}</div>`;
|
|
}
|
|
}
|
|
if (fileInput.files.length > 0) {
|
|
const file = fileInput.files[0];
|
|
const isImage = /\.(jpg|jpeg|png|gif|bmp|svg)$/i.test(file.name);
|
|
const isVideo = /\.(mp4|webm|ogg)$/i.test(file.name);
|
|
if (isImage) {
|
|
newMessage.innerHTML = `<img src="${imagePreview.src}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${formatLocalTime(timestamp)}</div>`;
|
|
} else if (isVideo) {
|
|
newMessage.innerHTML = `<video controls style="max-width: 200px; max-height: 200px;"><source src="${videoPreview.src}" type="video/mp4"></video><div class="timestamp">${formatLocalTime(timestamp)}</div>`;
|
|
} else {
|
|
newMessage.innerHTML = `<a href="${URL.createObjectURL(file)}" target="_blank">Download File</a><div class="timestamp">${formatLocalTime(timestamp)}</div>`;
|
|
}
|
|
}
|
|
messagesList.appendChild(newMessage);
|
|
scrollToBottom();
|
|
|
|
const formData = new FormData();
|
|
formData.append('content', content);
|
|
formData.append('timestamp', timestamp);
|
|
if (fileInput.files[0]) {
|
|
formData.append('file', fileInput.files[0]);
|
|
}
|
|
|
|
fetch(`/send_message/${receiver}`, {
|
|
method: 'POST',
|
|
body: formData
|
|
}).then(response => response.json())
|
|
.then(data => {
|
|
if (data.status === 'Message sent') {
|
|
contentInput.value = '';
|
|
fileInput.value = '';
|
|
document.querySelector('.file-preview').style.display = 'none';
|
|
document.querySelector('.file-preview img').src = '';
|
|
document.querySelector('.file-preview video').style.display = 'none';
|
|
document.querySelector('.file-preview video').src = '';
|
|
document.querySelector('.file-preview .file-preview-filename').style.display = 'none';
|
|
document.querySelector('.file-preview .file-preview-filename').textContent = '';
|
|
|
|
// Update the message ID with the real one from the server
|
|
newMessage.dataset.messageId = data.message_id;
|
|
} else {
|
|
showToast('Message sending failed');
|
|
}
|
|
}).catch(error => {
|
|
showToast('Message sending failed');
|
|
console.error('Error sending message:', error);
|
|
});
|
|
}
|
|
});
|
|
|
|
document.querySelector('input[name="file"]').addEventListener('change', function(event) {
|
|
const file = event.target.files[0];
|
|
if (file) {
|
|
const reader = new FileReader();
|
|
const imagePreview = document.querySelector('.file-preview-image');
|
|
const videoPreview = document.querySelector('.file-preview-video');
|
|
const filenamePreview = document.querySelector('.file-preview-filename');
|
|
reader.onload = function(e) {
|
|
document.querySelector('.file-preview').style.display = 'block';
|
|
if (file.type.startsWith('image/')) {
|
|
imagePreview.style.display = 'block';
|
|
videoPreview.style.display = 'none';
|
|
filenamePreview.style.display = 'none';
|
|
imagePreview.src = e.target.result;
|
|
} else if (file.type.startsWith('video/')) {
|
|
videoPreview.style.display = 'block';
|
|
imagePreview.style.display = 'none';
|
|
filenamePreview.style.display = 'none';
|
|
videoPreview.querySelector('source').src = e.target.result;
|
|
videoPreview.load();
|
|
} else {
|
|
filenamePreview.style.display = 'block';
|
|
imagePreview.style.display = 'none';
|
|
videoPreview.style.display = 'none';
|
|
filenamePreview.textContent = file.name;
|
|
}
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
});
|
|
|
|
document.querySelector('.remove-file').addEventListener('click', function() {
|
|
const fileInput = document.querySelector('input[name="file"]');
|
|
fileInput.value = '';
|
|
const filePreview = document.querySelector('.file-preview');
|
|
filePreview.style.display = 'none';
|
|
filePreview.querySelector('img').style.display = 'none';
|
|
filePreview.querySelector('img').src = '';
|
|
filePreview.querySelector('video').style.display = 'none';
|
|
filePreview.querySelector('video').src = '';
|
|
filePreview.querySelector('.file-preview-filename').style.display = 'none';
|
|
filePreview.querySelector('.file-preview-filename').textContent = '';
|
|
});
|
|
}
|
|
|
|
function checkFriendRequests() {
|
|
const friendRequestsList = document.getElementById('friend-requests');
|
|
const friendRequestsSection = document.getElementById('friend-requests-section');
|
|
if (friendRequestsList && friendRequestsSection) {
|
|
if (friendRequestsList.children.length === 0) {
|
|
friendRequestsSection.style.display = 'none';
|
|
} else {
|
|
friendRequestsSection.style.display = 'block';
|
|
}
|
|
}
|
|
}
|
|
|
|
checkFriendRequests();
|
|
|
|
// Image enhancer functionality
|
|
document.addEventListener('click', function(event) {
|
|
if (event.target.classList.contains('enhanceable-image')) {
|
|
overlayImage.src = event.target.src;
|
|
imageOverlay.style.display = 'flex';
|
|
}
|
|
});
|
|
|
|
imageOverlay.addEventListener('click', function() {
|
|
imageOverlay.style.display = 'none';
|
|
});
|
|
|
|
// Custom Context Menu Functionality
|
|
document.addEventListener('contextmenu', function(event) {
|
|
const messageElement = event.target.closest('.message');
|
|
if (messageElement) {
|
|
event.preventDefault();
|
|
currentMessageId = messageElement.dataset.messageId;
|
|
currentMessageText = messageElement.dataset.messageText;
|
|
|
|
contextMenu.style.top = `${event.clientY}px`;
|
|
contextMenu.style.left = `${event.clientX}px`;
|
|
contextMenu.style.display = 'block';
|
|
} else {
|
|
contextMenu.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
document.getElementById('copy-text').addEventListener('click', function() {
|
|
if (currentMessageText) {
|
|
navigator.clipboard.writeText(currentMessageText).then(() => {
|
|
showToast('Text copied to clipboard');
|
|
});
|
|
}
|
|
contextMenu.style.display = 'none';
|
|
});
|
|
|
|
document.getElementById('copy-id').addEventListener('click', function() {
|
|
if (currentMessageId) {
|
|
navigator.clipboard.writeText(currentMessageId).then(() => {
|
|
showToast('Message ID copied to clipboard');
|
|
});
|
|
}
|
|
contextMenu.style.display = 'none';
|
|
});
|
|
|
|
contextMenu.addEventListener('contextmenu', function(event) {
|
|
event.preventDefault();
|
|
});
|
|
|
|
document.addEventListener('click', function() {
|
|
contextMenu.style.display = 'none';
|
|
});
|
|
});
|