diff --git a/.gitignore b/.gitignore index b6e006c..9c050d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.db -secret.key +*.key +secret_key.json app.log /cdn /uploads diff --git a/app.py b/app.py index 64f017f..998f933 100644 --- a/app.py +++ b/app.py @@ -6,6 +6,7 @@ import hashlib import os import random import string +import json from datetime import datetime, timedelta, timezone from werkzeug.utils import secure_filename from flask_socketio import SocketIO, emit, join_room, leave_room @@ -19,8 +20,21 @@ logging.basicConfig(filename='app.log', level=logging.DEBUG, logger = logging.getLogger(__name__) app = Flask(__name__) -app.config['SECRET_KEY'] = os.urandom(24) -app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db' +app.config['SECRET_KEY'] = None # Initially set to None + +def load_secret_key(): + secret_key_path = 'secret_key.json' + if not os.path.exists(secret_key_path): + secret_key = os.urandom(24) + with open(secret_key_path, 'w') as key_file: + json.dump({'SECRET_KEY': secret_key.hex()}, key_file) + else: + with open(secret_key_path, 'r') as key_file: + secret_key = bytes.fromhex(json.load(key_file)['SECRET_KEY']) + return secret_key + +app.config['SECRET_KEY'] = load_secret_key() +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///W:/site.db' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['UPLOAD_FOLDER'] = 'cdn' os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) @@ -30,12 +44,12 @@ migrate = Migrate(app, db) # Load encryption key def load_key(): - return open("secret.key", "rb").read() + return open(r"W:\secret.key", "rb").read() # Ensure the key file exists -if not os.path.exists("secret.key"): +if not os.path.exists(r"W:\secret.key"): key = Fernet.generate_key() - with open("secret.key", "wb") as key_file: + with open(r"secret.key", "wb") as key_file: key_file.write(key) key = load_key() @@ -149,17 +163,31 @@ def register(): username = request.form['username'].lower() password = request.form['password'] hashed_password = sha256_password_hash(password) - new_user = User(username=username, password=hashed_password) + profile_picture = "default_profile_picture.png" # Set default profile picture + new_user = User(username=username, password=hashed_password, profile_picture=profile_picture) try: db.session.add(new_user) db.session.commit() logger.info(f"New user registered: {username}") - return redirect(url_for('login')) + + # Automatically log the user in after registration + session['username'] = new_user.username + session['user_id'] = new_user.id + session['is_admin'] = new_user.is_admin + + token = generate_token(new_user.id) + response = make_response(redirect(url_for('dashboard'))) + expires = datetime.now(timezone.utc) + timedelta(days=7) + response.set_cookie('token', token, httponly=True, expires=expires) + + logger.info(f"User logged in: {username} after registration") + return response except Exception as e: logger.error(f"Error registering user {username}: {e}") return 'Username already exists!' return render_template('register.html') + @app.route('/login', methods=['GET', 'POST']) def login(): if 'username' in session: @@ -300,8 +328,12 @@ def dashboard(): except InvalidToken: decrypted_messages.append((msg.sender, "Invalid encrypted message", msg.timestamp)) + profile_picture = user.profile_picture if user.profile_picture else 'default_profile_picture.png' + for friend in friend_users: + friend.profile_picture = friend.profile_picture if friend.profile_picture else 'default_profile_picture.png' + logger.info(f"User {session['username']} accessed dashboard") - return render_template('dashboard.html', username=session['username'], user_id=user.id, friends=friend_users, friend_requests=friend_requests, pending_messages=decrypted_pending_messages, messages=decrypted_messages, groups=groups, is_admin=user.is_admin) + return render_template('dashboard.html', username=session['username'], user_id=user.id, friends=friend_users, friend_requests=friend_requests, pending_messages=decrypted_pending_messages, messages=decrypted_messages, groups=groups, is_admin=user.is_admin, profile_picture=profile_picture) return redirect(url_for('login')) @app.route('/chat/') @@ -401,7 +433,7 @@ def get_messages(friend_id): { 'id': msg.id, 'sender': msg.sender.username, - 'sender_profile_picture': f'/cdn/{msg.sender.profile_picture}' if msg.sender.profile_picture else None, + 'sender_profile_picture': f'{msg.sender.profile_picture}' if msg.sender.profile_picture else None, 'content': cipher.decrypt(msg.content.encode()).decode() if msg.content_type == 'text' else msg.content, 'content_type': msg.content_type, 'timestamp': msg.timestamp.strftime("%Y-%m-%d %H:%M:%S") @@ -782,7 +814,7 @@ def handle_connect(): user = User.query.filter_by(username=session['username']).first() if user: user.online = True - user.last_activity = datetime.utcnow() + user.last_activity = datetime.now(timezone.utc) db.session.commit() emit('user_online', {'username': user.username}, broadcast=True) logger.info(f"User {session['username']} connected") @@ -797,14 +829,60 @@ def handle_disconnect(): emit('user_offline', {'username': user.username}, broadcast=True) logger.info(f"User {session['username']} disconnected") +@socketio.on('send_message') +def handle_send_message(data): + sender = session['username'] + receiver_id = data['receiver_id'] + message = data['message'] + timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + + # Save the message in the database + new_message = Message(sender_id=User.query.filter_by(username=sender).first().id, receiver_id=receiver_id, content=message, timestamp=timestamp) + db.session.add(new_message) + db.session.commit() + + # Emit the message to the receiver if they are online and not in the same chat + receiver = User.query.get(receiver_id) + if receiver.online: + emit('new_message', { + 'sender': sender, + 'message': message, + 'timestamp': timestamp, + 'receiver_id': receiver_id + }, room=receiver.username) + +@socketio.on('send_group_message') +def handle_send_group_message(data): + sender = session['username'] + group_id = data['group_id'] + message = data['message'] + timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + + # Save the message in the database + new_group_message = GroupMessage(sender_id=User.query.filter_by(username=sender).first().id, group_id=group_id, content=message, timestamp=timestamp) + db.session.add(new_group_message) + db.session.commit() + + # Emit the message to the group if they are online and not in the same group chat + group_members = GroupMember.query.filter_by(group_id=group_id).all() + for member in group_members: + user = User.query.get(member.user_id) + if user.username != sender and user.online: + emit('new_group_message', { + 'sender': sender, + 'message': message, + 'timestamp': timestamp, + 'group_id': group_id + }, room=user.username) + @socketio.on('join') -def handle_join(data): +def on_join(data): username = data['username'] join_room(username) logger.info(f"User {username} joined room {username}") @socketio.on('leave') -def handle_leave(data): +def on_leave(data): username = data['username'] leave_room(username) logger.info(f"User {username} left room {username}") @@ -823,23 +901,6 @@ def handle_leave_group(data): leave_room(f"group_{group_id}") logger.info(f"User {username} left group room {group_id}") -@socketio.on('user_activity') -def handle_user_activity(): - if 'username' in session: - user = User.query.filter_by(username=session['username']).first() - if user: - user.last_activity = datetime.utcnow() - db.session.commit() - -def set_users_to_idle(): - with app.app_context(): - idle_threshold = datetime.utcnow() - timedelta(minutes=5) - users = User.query.filter(User.online == True, User.last_activity < idle_threshold).all() - for user in users: - user.online = False - db.session.commit() - emit('user_idle', {'username': user.username}, broadcast=True) - if __name__ == '__main__': with app.app_context(): db.create_all() diff --git a/instance/site.db.lnk b/instance/site.db.lnk new file mode 100644 index 0000000..b76ae07 Binary files /dev/null and b/instance/site.db.lnk differ diff --git a/requirements.txt b/requirements.txt index 7ff1ad8..d4fecd6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,9 +2,11 @@ Flask==3.0.3 Flask-SQLAlchemy==3.1.1 Flask-SocketIO==5.3.6 Flask-Migrate==4.0.7 +Flask-Session==0.8.0 cryptography==42.0.8 Werkzeug==3.0.3 python-socketio==5.11.2 python-engineio==4.9.1 PyJWT==2.8.0 -Pillow==10.3.0 \ No newline at end of file +Pillow==10.3.0 +redis==5.0.6 \ No newline at end of file diff --git a/static/js/chat.js b/static/js/chat.js index d3ca42f..11a0282 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -1,21 +1,5 @@ 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}`; @@ -23,352 +7,9 @@ document.addEventListener('DOMContentLoaded', (event) => { 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 = ` - /cdn/${msg.sender} - ${msg.sender} -
${formatLocalTime(msg.timestamp)}
`; - messagesList.appendChild(usernameElement); - } - - if (msg.content_type === 'text') { - if (isImageUrl(msg.content)) { - newMessage.innerHTML = `Image
${formatLocalTime(msg.timestamp)}
`; - } else { - newMessage.innerHTML = `${md.render(msg.content)}
${formatLocalTime(msg.timestamp)}
`; - } - } else if (msg.content_type === 'file') { - const isImage = isImageUrl(msg.content); - const isVideo = /\.(mp4|webm|ogg)$/i.test(msg.content); - if (isImage) { - newMessage.innerHTML = `Image
${formatLocalTime(msg.timestamp)}
`; - } else if (isVideo) { - newMessage.innerHTML = `
${formatLocalTime(msg.timestamp)}
`; - } else { - newMessage.innerHTML = `Download File
${formatLocalTime(msg.timestamp)}
`; - } - } - - 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 ? `${data.sender}` : ''; - - usernameElement.innerHTML = `${profilePicture} ${data.sender}
${timestampFormatted}
`; - messagesList.appendChild(usernameElement); - } - - if (data.content_type === 'text') { - if (isImageUrl(data.content)) { - newMessage.innerHTML = `Image
${formatLocalTime(data.timestamp)}
`; - } else { - newMessage.innerHTML = `${md.render(data.content)}
${formatLocalTime(data.timestamp)}
`; - } - } else if (data.content_type === 'file') { - const isImage = isImageUrl(data.content); - const isVideo = /\.(mp4|webm|ogg)$/i.test(data.content); - if (isImage) { - newMessage.innerHTML = `Image
${formatLocalTime(data.timestamp)}
`; - } else if (isVideo) { - newMessage.innerHTML = `
${formatLocalTime(data.timestamp)}
`; - } else { - newMessage.innerHTML = `Download File
${formatLocalTime(data.timestamp)}
`; - } - } - - 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 ? `${msg.sender}` : ''; - usernameElement.innerHTML = `${profilePicture} ${msg.sender}
${formatLocalTime(msg.timestamp)}
`; - 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 = `Image
${formatLocalTime(msg.timestamp)}
`; - } else { - messageElement.innerHTML = `${md.render(msg.content)}
${formatLocalTime(msg.timestamp)}
`; - } - } else if (msg.content_type === 'file') { - const isImage = isImageUrl(msg.content); - const isVideo = /\.(mp4|webm|ogg)$/i.test(msg.content); - if (isImage) { - messageElement.innerHTML = `Image
${formatLocalTime(msg.timestamp)}
`; - } else if (isVideo) { - messageElement.innerHTML = `
${formatLocalTime(msg.timestamp)}
`; - } else { - messageElement.innerHTML = `Download File
${formatLocalTime(msg.timestamp)}
`; - } - } - - 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 = `${username} ${username}
${timestampFormatted}
`; - messagesList.appendChild(usernameElement); - } - - - - if (content) { - newMessage.dataset.messageText = content; - if (isImageUrl(content)) { - newMessage.innerHTML = `Image
${formatLocalTime(timestamp)}
`; - } else { - newMessage.innerHTML = `${md.render(content)}
${formatLocalTime(timestamp)}
`; - } - } - 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 = `Image
${formatLocalTime(timestamp)}
`; - } else if (isVideo) { - newMessage.innerHTML = `
${formatLocalTime(timestamp)}
`; - } else { - newMessage.innerHTML = `Download File
${formatLocalTime(timestamp)}
`; - } - } - 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'); @@ -383,57 +24,115 @@ document.addEventListener('DOMContentLoaded', (event) => { checkFriendRequests(); - // Image enhancer functionality - document.addEventListener('click', function(event) { + socket.on('new_message', function (data) { + const currentTimestamp = new Date(data.timestamp).getTime(); + const showUsername = previousSender !== data.sender || shouldShowUsername(previousTimestamp, currentTimestamp); + previousSender = data.sender; + previousTimestamp = currentTimestamp; + + const profilePicUrl = data.sender_profile_picture ? `${data.sender_profile_picture}` : 'default_profile_picture.png'; + appendMessage(messagesList, data, showUsername, profilePicUrl); + }); + + 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); + + const profilePicUrl = msg.sender_profile_picture ? `${msg.sender_profile_picture}` : 'default_profile_picture.png'; + appendMessage(messagesList, msg, showUsername, profilePicUrl); + }); + + scrollToBottom(messagesList); + }) + .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) { + 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 = ''; + + // Append the message immediately for the sender + const profilePictureUrl = profilePicture ? `${profilePicture}` : 'default_profile_picture.png'; + const messageData = { + sender: username, + content: content, + timestamp: timestamp, + sender_profile_picture: profilePictureUrl, + content_type: fileInput.files.length > 0 ? 'file' : 'text', + id: data.message_id + }; + + const currentTimestamp = new Date(messageData.timestamp).getTime(); + const showUsername = previousSender !== username || shouldShowUsername(previousTimestamp, currentTimestamp); + previousSender = username; + previousTimestamp = currentTimestamp; + + appendMessage(messagesList, messageData, showUsername, profilePictureUrl); + } else { + showToast('Message sending failed'); + } + }).catch(error => { + showToast('Message sending failed'); + console.error('Error sending message:', error); + }); + } + }); + + document.querySelector('input[name="file"]').addEventListener('change', handleFileInputChange); + + document.querySelector('.remove-file').addEventListener('click', handleRemoveFile); + } + + 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.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'; - }); }); diff --git a/static/js/common.js b/static/js/common.js new file mode 100644 index 0000000..41cdec8 --- /dev/null +++ b/static/js/common.js @@ -0,0 +1,136 @@ +// common.js +const md = window.markdownit(); + +const contextMenu = document.getElementById('context-menu'); +let currentMessageId = null; +let currentMessageText = null; + +function showToast(message) { + const toast = document.getElementById('toast'); + const toastMessage = document.getElementById('toast-message'); + toastMessage.textContent = message; + toast.classList.add('show'); + setTimeout(() => { + toast.classList.remove('show'); + }, 3000); +} + +function scrollToBottom(element) { + element.scrollTop = element.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(messagesList, msg, showUsername, senderProfilePicture) { + const currentTimestamp = new Date(msg.timestamp).getTime(); + + const newMessage = document.createElement('div'); + newMessage.classList.add('message'); + newMessage.dataset.messageId = msg.id; + newMessage.dataset.messageText = msg.content; + + if (showUsername) { + const usernameElement = document.createElement('div'); + usernameElement.classList.add('message-sender'); + if (msg.sender === username) { + usernameElement.classList.add('message-myself-sender'); + } + const profilePicUrl = senderProfilePicture ? `/cdn/${senderProfilePicture}` : '/cdn/default_profile_picture.png'; + usernameElement.innerHTML = ` + ${msg.sender} + ${msg.sender} +
${formatLocalTime(msg.timestamp)}
`; + messagesList.appendChild(usernameElement); + } + + if (msg.content_type === 'text') { + if (isImageUrl(msg.content)) { + newMessage.innerHTML = `Image
${formatLocalTime(msg.timestamp)}
`; + } else { + newMessage.innerHTML = `${md.render(msg.content)}
${formatLocalTime(msg.timestamp)}
`; + } + } else if (msg.content_type === 'file') { + const isImage = isImageUrl(msg.content); + const isVideo = /\.(mp4|webm|ogg)$/i.test(msg.content); + if (isImage) { + newMessage.innerHTML = `Image
${formatLocalTime(msg.timestamp)}
`; + } else if (isVideo) { + newMessage.innerHTML = `
${formatLocalTime(msg.timestamp)}
`; + } else { + newMessage.innerHTML = `Download File
${formatLocalTime(msg.timestamp)}
`; + } + } + + messagesList.appendChild(newMessage); + scrollToBottom(messagesList); +} + +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'; +}); diff --git a/static/js/connection_check.js b/static/js/connection_check.js index 5c2f3fc..03ede71 100644 --- a/static/js/connection_check.js +++ b/static/js/connection_check.js @@ -12,9 +12,7 @@ document.addEventListener('DOMContentLoaded', (event) => { setInterval(() => { if (!isConnected) { - if (confirm('Something went wrong, please reload the website')) { - window.location.reload(); - } + window.location.reload(); } isConnected = false; checkConnection(); @@ -26,8 +24,6 @@ document.addEventListener('DOMContentLoaded', (event) => { socket.on('disconnect', () => { isConnected = false; - if (confirm('Something went wrong, please reload the website')) { - window.location.reload(); - } + window.location.reload(); }); }); diff --git a/static/js/group_chat.js b/static/js/group_chat.js index 4013eb6..9971798 100644 --- a/static/js/group_chat.js +++ b/static/js/group_chat.js @@ -1,6 +1,5 @@ document.addEventListener('DOMContentLoaded', (event) => { const socket = io(); - const md = window.markdownit(); socket.emit('join_group', { group_id: groupId }); document.title = `${groupName}`; @@ -9,166 +8,62 @@ document.addEventListener('DOMContentLoaded', (event) => { 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); - } - let previousSender = null; let previousTimestamp = 0; - socket.on('new_group_message', function(data) { + 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(); + + socket.on('new_group_message', function (data) { const currentTimestamp = new Date(data.timestamp).getTime(); const showUsername = previousSender !== data.sender || shouldShowUsername(previousTimestamp, currentTimestamp); previousSender = data.sender; previousTimestamp = currentTimestamp; - - 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 profilePictureUrl = `/cdn/${data.sender_profile_picture}`; - usernameElement.innerHTML = `${data.sender} ${data.sender}
${formatLocalTime(data.timestamp)}
`; - messagesList.appendChild(usernameElement); - } - - if (data.content_type === 'text') { - if (isImageUrl(data.content)) { - newMessage.innerHTML = `Image
${formatLocalTime(data.timestamp)}
`; - } else { - newMessage.innerHTML = `${md.render(data.content)}
${formatLocalTime(data.timestamp)}
`; - } - } else if (data.content_type === 'file') { - const isImage = isImageUrl(data.content); - const isVideo = /\.(mp4|webm|ogg)$/i.test(data.content); - if (isImage) { - newMessage.innerHTML = `Image
${formatLocalTime(data.timestamp)}
`; - } else if (isVideo) { - newMessage.innerHTML = `
${formatLocalTime(data.timestamp)}
`; - } else { - newMessage.innerHTML = `Download File
${formatLocalTime(data.timestamp)}
`; - } - } - - messagesList.appendChild(newMessage); - scrollToBottom(); - }); + + const profilePicUrl = data.sender_profile_picture ? `${data.sender_profile_picture}` : 'default_profile_picture.png'; + appendMessage(messagesList, data, showUsername, profilePicUrl); + }); if (groupId) { fetch(`/get_group_messages/${groupId}`) - .then(response => { - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - return response.json(); - }) - .then(data => { - messagesList.innerHTML = ''; - document.getElementById('chat-with').textContent = `${groupName}`; - document.getElementById('send-message-form').dataset.receiver = groupId; + .then(response => response.json()) + .then(data => { + messagesList.innerHTML = ''; + document.getElementById('chat-with').textContent = `${groupName}`; + document.getElementById('send-message-form').dataset.receiver = groupId; - 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'); - } - const profilePictureUrl = `/cdn/${msg.sender_profile_picture}`; - usernameElement.innerHTML = `${msg.sender} ${msg.sender}
${formatLocalTime(msg.timestamp)}
`; - messagesList.appendChild(usernameElement); - } - - - - const messageElement = document.createElement('div'); - messageElement.classList.add('message'); + 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); - messageElement.dataset.messageId = msg.id; - messageElement.dataset.messageText = msg.content; - - if (msg.content_type === 'text') { - messageElement.innerHTML = `${md.render(msg.content)}
${msg.timestamp}
`; - } else if (msg.content_type === 'file') { - const isImage = /\.(jpg|jpeg|png|gif|bmp|svg)$/i.test(msg.content); - const isVideo = /\.(mp4|webm|ogg)$/i.test(msg.content); - if (isImage) { - messageElement.innerHTML = `Image
${msg.timestamp}
`; - } else if (isVideo) { - messageElement.innerHTML = `
${msg.timestamp}
`; - } else { - messageElement.innerHTML = `Download File
${msg.timestamp}
`; - } - } - - messagesList.appendChild(messageElement); - }); - - scrollToBottom(); - }) - .catch(error => { - console.error('Error fetching group messages:', error); - alert('Error fetching group messages: ' + error.message); - }); + const profilePicUrl = msg.sender_profile_picture ? `${msg.sender_profile_picture}` : 'default_profile_picture.png'; + appendMessage(messagesList, msg, showUsername, profilePicUrl); + }); + scrollToBottom(messagesList); + }) + .catch(error => console.error('Error fetching group messages:', error)); } const sendMessageForm = document.querySelector('.send-message-form'); if (sendMessageForm) { - sendMessageForm.addEventListener('submit', function(event) { + sendMessageForm.addEventListener('submit', function (event) { event.preventDefault(); const receiver = this.dataset.receiver; const contentInput = this.querySelector('input[name="content"]'); @@ -187,162 +82,61 @@ document.addEventListener('DOMContentLoaded', (event) => { formData.append('file', fileInput.files[0]); } - fetch(`/send_group_message/${groupId}`, { + fetch(`/send_group_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 = ''; + .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 = ''; - // The message will be appended only when the server confirms it - socket.emit('new_group_message', { - group_id: groupId, - sender: username, - content: content, - content_type: 'text', // Adjust this as necessary - timestamp: timestamp, - id: data.message_id - }); - } else { - showToast('Message sending failed'); - } - }).catch(error => { - showToast('Message sending failed'); - console.error('Error sending group message:', error); - }); + // Append the message immediately for the sender + const profilePictureUrl = profilePicture ? `${profilePicture}` : 'default_profile_picture.png'; + const messageData = { + sender: username, + content: content, + timestamp: timestamp, + sender_profile_picture: profilePictureUrl, + content_type: fileInput.files.length > 0 ? 'file' : 'text', + id: data.message_id + }; + + const currentTimestamp = new Date(messageData.timestamp).getTime(); + const showUsername = previousSender !== username || shouldShowUsername(previousTimestamp, currentTimestamp); + previousSender = username; + previousTimestamp = currentTimestamp; + + appendMessage(messagesList, messageData, showUsername, profilePictureUrl); + } else { + showToast('Message sending failed'); + } + }).catch(error => { + showToast('Message sending failed'); + console.error('Error sending message:', error); + }); } }); - // Handle Shift + Enter for new line - const messageInput = document.querySelector('input[name="content"]'); - messageInput.addEventListener('keydown', function(event) { - if (event.key === 'Enter' && event.shiftKey) { - event.preventDefault(); - const start = this.selectionStart; - const end = this.selectionEnd; - this.value = this.value.substring(0, start) + "\n" + this.value.substring(end); - this.selectionStart = this.selectionEnd = start + 1; - } - }); + document.querySelector('input[name="file"]').addEventListener('change', handleFileInputChange); - 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 = ''; - }); + document.querySelector('.remove-file').addEventListener('click', handleRemoveFile); } - 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) { + 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.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'; - }); }); diff --git a/static/js/notification.js b/static/js/notification.js index 578de46..6fa408f 100644 --- a/static/js/notification.js +++ b/static/js/notification.js @@ -1,25 +1,58 @@ document.addEventListener('DOMContentLoaded', (event) => { - function requestNotificationPermission() { - if (Notification.permission !== "granted") { - Notification.requestPermission(); + const socket = io(); + + let isConnected = true; + + function checkConnection() { + socket.emit('ping'); + } + + socket.on('pong', function () { + isConnected = true; + }); + + setInterval(() => { + if (!isConnected) { + window.location.reload(); } + isConnected = false; + checkConnection(); + }, 3000); + + socket.on('connect', () => { + isConnected = true; + }); + + socket.on('disconnect', () => { + isConnected = false; + window.location.reload(); + }); + + // Function to check if the user is focused on the website + function isUserFocused() { + return document.hasFocus(); } - function showNotification(title, body) { - if (Notification.permission === "granted") { - new Notification(title, { body }); + // Function to check if the user is in the same chat or group chat + function isUserInSameChat(sender, receiverId, groupId) { + const currentChatUserId = document.getElementById('current-chat-user-id'); + const currentGroupId = document.getElementById('current-group-id'); + return (currentChatUserId && currentChatUserId.value == receiverId) || (currentGroupId && currentGroupId.value == groupId); + } + + socket.on('new_message', (data) => { + const { sender, message, timestamp, receiver_id } = data; + if (!isUserInSameChat(sender, receiver_id, null) || !isUserFocused()) { + // Display notification logic here + console.log(`New message from ${sender}: ${message}`); } - } + }); - requestNotificationPermission(); - - function handleMessageNotification(data) { - const title = `New Message from ${data.sender}`; - const body = data.content.length > 100 ? data.content.substring(0, 97) + '...' : data.content; - showNotification(title, body); - } - - window.notificationHandler = { - handleMessageNotification - }; + socket.on('new_group_message', (data) => { + const { sender, message, timestamp, group_id } = data; + if (!isUserInSameChat(sender, null, group_id) || !isUserFocused()) { + // Display notification logic here + console.log(`New group message from ${sender}: ${message}`); + } + }); }); diff --git a/static/js/upload_handler.js b/static/js/upload_handler.js new file mode 100644 index 0000000..8bba5fc --- /dev/null +++ b/static/js/upload_handler.js @@ -0,0 +1,44 @@ +// upload_handler.js +function handleFileInputChange(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); + } +} + +function handleRemoveFile() { + 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 = ''; +} diff --git a/static/output.css b/static/output.css index 7f4998e..775229a 100644 --- a/static/output.css +++ b/static/output.css @@ -640,6 +640,10 @@ video { display: table; } +.hidden { + display: none; +} + .\!h-\[400px\] { height: 400px !important; } diff --git a/templates/head.html b/templates/head.html index 173aef5..bf4f1b3 100644 --- a/templates/head.html +++ b/templates/head.html @@ -1,43 +1,46 @@ - - - {% block title %}{% endblock %} - - - - - - - - - - + + +{% block title %}{% endblock %} + + + + + + + + + + + + + - - {% if 'username' in session %} - - {% endif %} \ No newline at end of file + // Listen for changes to the system theme and apply if the user selected 'automatic' + if (currentTheme === 'automatic') { + window.matchMedia('(prefers-color-scheme: dark)').addListener(e => { + applyTheme('automatic'); + }); + } + }); + +{% if 'username' in session %} + +{% endif %}