Up to date Version

This commit is contained in:
Olai Vike Bøe 2024-09-10 11:53:00 +02:00
parent bc9fcda6aa
commit 2db08a537c
12 changed files with 552 additions and 779 deletions

3
.gitignore vendored
View file

@ -1,5 +1,6 @@
*.db *.db
secret.key *.key
secret_key.json
app.log app.log
/cdn /cdn
/uploads /uploads

119
app.py
View file

@ -6,6 +6,7 @@ import hashlib
import os import os
import random import random
import string import string
import json
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from flask_socketio import SocketIO, emit, join_room, leave_room 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__) logger = logging.getLogger(__name__)
app = Flask(__name__) app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24) app.config['SECRET_KEY'] = None # Initially set to None
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
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['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['UPLOAD_FOLDER'] = 'cdn' app.config['UPLOAD_FOLDER'] = 'cdn'
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
@ -30,12 +44,12 @@ migrate = Migrate(app, db)
# Load encryption key # Load encryption key
def load_key(): def load_key():
return open("secret.key", "rb").read() return open(r"W:\secret.key", "rb").read()
# Ensure the key file exists # 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() 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_file.write(key)
key = load_key() key = load_key()
@ -149,17 +163,31 @@ def register():
username = request.form['username'].lower() username = request.form['username'].lower()
password = request.form['password'] password = request.form['password']
hashed_password = sha256_password_hash(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: try:
db.session.add(new_user) db.session.add(new_user)
db.session.commit() db.session.commit()
logger.info(f"New user registered: {username}") 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: except Exception as e:
logger.error(f"Error registering user {username}: {e}") logger.error(f"Error registering user {username}: {e}")
return 'Username already exists!' return 'Username already exists!'
return render_template('register.html') return render_template('register.html')
@app.route('/login', methods=['GET', 'POST']) @app.route('/login', methods=['GET', 'POST'])
def login(): def login():
if 'username' in session: if 'username' in session:
@ -300,8 +328,12 @@ def dashboard():
except InvalidToken: except InvalidToken:
decrypted_messages.append((msg.sender, "Invalid encrypted message", msg.timestamp)) 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") 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')) return redirect(url_for('login'))
@app.route('/chat/<int:friend_id>') @app.route('/chat/<int:friend_id>')
@ -401,7 +433,7 @@ def get_messages(friend_id):
{ {
'id': msg.id, 'id': msg.id,
'sender': msg.sender.username, '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': cipher.decrypt(msg.content.encode()).decode() if msg.content_type == 'text' else msg.content,
'content_type': msg.content_type, 'content_type': msg.content_type,
'timestamp': msg.timestamp.strftime("%Y-%m-%d %H:%M:%S") '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() user = User.query.filter_by(username=session['username']).first()
if user: if user:
user.online = True user.online = True
user.last_activity = datetime.utcnow() user.last_activity = datetime.now(timezone.utc)
db.session.commit() db.session.commit()
emit('user_online', {'username': user.username}, broadcast=True) emit('user_online', {'username': user.username}, broadcast=True)
logger.info(f"User {session['username']} connected") logger.info(f"User {session['username']} connected")
@ -797,14 +829,60 @@ def handle_disconnect():
emit('user_offline', {'username': user.username}, broadcast=True) emit('user_offline', {'username': user.username}, broadcast=True)
logger.info(f"User {session['username']} disconnected") 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') @socketio.on('join')
def handle_join(data): def on_join(data):
username = data['username'] username = data['username']
join_room(username) join_room(username)
logger.info(f"User {username} joined room {username}") logger.info(f"User {username} joined room {username}")
@socketio.on('leave') @socketio.on('leave')
def handle_leave(data): def on_leave(data):
username = data['username'] username = data['username']
leave_room(username) leave_room(username)
logger.info(f"User {username} left 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}") leave_room(f"group_{group_id}")
logger.info(f"User {username} left group room {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__': if __name__ == '__main__':
with app.app_context(): with app.app_context():
db.create_all() db.create_all()

BIN
instance/site.db.lnk Normal file

Binary file not shown.

View file

@ -2,9 +2,11 @@ Flask==3.0.3
Flask-SQLAlchemy==3.1.1 Flask-SQLAlchemy==3.1.1
Flask-SocketIO==5.3.6 Flask-SocketIO==5.3.6
Flask-Migrate==4.0.7 Flask-Migrate==4.0.7
Flask-Session==0.8.0
cryptography==42.0.8 cryptography==42.0.8
Werkzeug==3.0.3 Werkzeug==3.0.3
python-socketio==5.11.2 python-socketio==5.11.2
python-engineio==4.9.1 python-engineio==4.9.1
PyJWT==2.8.0 PyJWT==2.8.0
Pillow==10.3.0 Pillow==10.3.0
redis==5.0.6

View file

@ -1,21 +1,5 @@
document.addEventListener('DOMContentLoaded', (event) => { document.addEventListener('DOMContentLoaded', (event) => {
const socket = io(); 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 }); socket.emit('join', { username: username });
document.title = `${friendUsername}`; document.title = `${friendUsername}`;
@ -23,157 +7,33 @@ document.addEventListener('DOMContentLoaded', (event) => {
const messagesList = document.getElementById('messages'); const messagesList = document.getElementById('messages');
const imageOverlay = document.getElementById('image-overlay'); const imageOverlay = document.getElementById('image-overlay');
const overlayImage = document.getElementById('overlay-image'); 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 previousSender = null;
let previousTimestamp = 0; let previousTimestamp = 0;
socket.on('new_message', function(data) { function checkFriendRequests() {
// Check if the message is for the current chat const friendRequestsList = document.getElementById('friend-requests');
if (friendUsername !== data.sender && friendId != data.sender_id) { const friendRequestsSection = document.getElementById('friend-requests-section');
return; if (friendRequestsList && friendRequestsSection) {
if (friendRequestsList.children.length === 0) {
friendRequestsSection.style.display = 'none';
} else {
friendRequestsSection.style.display = 'block';
}
}
} }
showNotification('New Message', `You have received a new message from ${data.sender}`); checkFriendRequests();
socket.on('new_message', function (data) {
const currentTimestamp = new Date(data.timestamp).getTime(); const currentTimestamp = new Date(data.timestamp).getTime();
const showUsername = previousSender !== data.sender || shouldShowUsername(previousTimestamp, currentTimestamp); const showUsername = previousSender !== data.sender || shouldShowUsername(previousTimestamp, currentTimestamp);
previousSender = data.sender; previousSender = data.sender;
previousTimestamp = currentTimestamp; previousTimestamp = currentTimestamp;
const messageElement = document.createElement('div'); const profilePicUrl = data.sender_profile_picture ? `${data.sender_profile_picture}` : 'default_profile_picture.png';
messageElement.classList.add('message'); appendMessage(messagesList, data, showUsername, profilePicUrl);
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) { if (friendId) {
fetch(`/get_messages/${friendId}`) fetch(`/get_messages/${friendId}`)
.then(response => response.json()) .then(response => response.json())
@ -188,51 +48,15 @@ document.addEventListener('DOMContentLoaded', (event) => {
const previousTimestamp = previousMessage ? new Date(previousMessage.timestamp).getTime() : 0; const previousTimestamp = previousMessage ? new Date(previousMessage.timestamp).getTime() : 0;
const showUsername = index === 0 || msg.sender !== previousMessage.sender || shouldShowUsername(previousTimestamp, currentTimestamp); const showUsername = index === 0 || msg.sender !== previousMessage.sender || shouldShowUsername(previousTimestamp, currentTimestamp);
if (showUsername) { const profilePicUrl = msg.sender_profile_picture ? `${msg.sender_profile_picture}` : 'default_profile_picture.png';
const usernameElement = document.createElement('div'); appendMessage(messagesList, msg, showUsername, profilePicUrl);
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(); scrollToBottom(messagesList);
}) })
.catch(error => console.error('Error fetching messages:', error)); .catch(error => console.error('Error fetching messages:', error));
} }
const sendMessageForm = document.querySelector('.send-message-form'); const sendMessageForm = document.querySelector('.send-message-form');
if (sendMessageForm) { if (sendMessageForm) {
sendMessageForm.addEventListener('submit', function (event) { sendMessageForm.addEventListener('submit', function (event) {
@ -247,49 +71,6 @@ document.addEventListener('DOMContentLoaded', (event) => {
const filenamePreview = document.querySelector('.file-preview-filename'); const filenamePreview = document.querySelector('.file-preview-filename');
if (content || fileInput.files.length > 0) { 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(); const formData = new FormData();
formData.append('content', content); formData.append('content', content);
formData.append('timestamp', timestamp); formData.append('timestamp', timestamp);
@ -312,8 +93,23 @@ document.addEventListener('DOMContentLoaded', (event) => {
document.querySelector('.file-preview .file-preview-filename').style.display = 'none'; document.querySelector('.file-preview .file-preview-filename').style.display = 'none';
document.querySelector('.file-preview .file-preview-filename').textContent = ''; document.querySelector('.file-preview .file-preview-filename').textContent = '';
// Update the message ID with the real one from the server // Append the message immediately for the sender
newMessage.dataset.messageId = data.message_id; 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 { } else {
showToast('Message sending failed'); showToast('Message sending failed');
} }
@ -324,66 +120,11 @@ document.addEventListener('DOMContentLoaded', (event) => {
} }
}); });
document.querySelector('input[name="file"]').addEventListener('change', function(event) { document.querySelector('input[name="file"]').addEventListener('change', handleFileInputChange);
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() { document.querySelector('.remove-file').addEventListener('click', 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 = '';
});
} }
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')) { if (event.target.classList.contains('enhanceable-image')) {
overlayImage.src = event.target.src; overlayImage.src = event.target.src;
@ -394,46 +135,4 @@ document.addEventListener('DOMContentLoaded', (event) => {
imageOverlay.addEventListener('click', function () { imageOverlay.addEventListener('click', function () {
imageOverlay.style.display = 'none'; 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';
});
}); });

136
static/js/common.js Normal file
View file

@ -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 = `
<img src="${profilePicUrl}" alt="${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(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';
});

View file

@ -12,10 +12,8 @@ document.addEventListener('DOMContentLoaded', (event) => {
setInterval(() => { setInterval(() => {
if (!isConnected) { if (!isConnected) {
if (confirm('Something went wrong, please reload the website')) {
window.location.reload(); window.location.reload();
} }
}
isConnected = false; isConnected = false;
checkConnection(); checkConnection();
}, 3000); }, 3000);
@ -26,8 +24,6 @@ document.addEventListener('DOMContentLoaded', (event) => {
socket.on('disconnect', () => { socket.on('disconnect', () => {
isConnected = false; isConnected = false;
if (confirm('Something went wrong, please reload the website')) {
window.location.reload(); window.location.reload();
}
}); });
}); });

View file

@ -1,6 +1,5 @@
document.addEventListener('DOMContentLoaded', (event) => { document.addEventListener('DOMContentLoaded', (event) => {
const socket = io(); const socket = io();
const md = window.markdownit();
socket.emit('join_group', { group_id: groupId }); socket.emit('join_group', { group_id: groupId });
document.title = `${groupName}`; document.title = `${groupName}`;
@ -9,107 +8,39 @@ document.addEventListener('DOMContentLoaded', (event) => {
const imageOverlay = document.getElementById('image-overlay'); const imageOverlay = document.getElementById('image-overlay');
const overlayImage = document.getElementById('overlay-image'); const overlayImage = document.getElementById('overlay-image');
const contextMenu = document.getElementById('context-menu'); const contextMenu = document.getElementById('context-menu');
const toast = document.getElementById('toast');
const toastMessage = document.getElementById('toast-message');
let currentMessageId = null; let currentMessageId = null;
let currentMessageText = 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 previousSender = null;
let previousTimestamp = 0; let previousTimestamp = 0;
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) { socket.on('new_group_message', function (data) {
const currentTimestamp = new Date(data.timestamp).getTime(); const currentTimestamp = new Date(data.timestamp).getTime();
const showUsername = previousSender !== data.sender || shouldShowUsername(previousTimestamp, currentTimestamp); const showUsername = previousSender !== data.sender || shouldShowUsername(previousTimestamp, currentTimestamp);
previousSender = data.sender; previousSender = data.sender;
previousTimestamp = currentTimestamp; previousTimestamp = currentTimestamp;
const newMessage = document.createElement('div'); const profilePicUrl = data.sender_profile_picture ? `${data.sender_profile_picture}` : 'default_profile_picture.png';
newMessage.classList.add('message'); appendMessage(messagesList, data, showUsername, profilePicUrl);
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 = `<img src="${profilePictureUrl}" alt="${data.sender}" class="profile-picture enhanceable-image"> <strong>${data.sender}</strong><div class="message-sender-timestamp">${formatLocalTime(data.timestamp)}</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 (groupId) { if (groupId) {
fetch(`/get_group_messages/${groupId}`) fetch(`/get_group_messages/${groupId}`)
.then(response => { .then(response => response.json())
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => { .then(data => {
messagesList.innerHTML = ''; messagesList.innerHTML = '';
document.getElementById('chat-with').textContent = `${groupName}`; document.getElementById('chat-with').textContent = `${groupName}`;
@ -121,49 +52,13 @@ document.addEventListener('DOMContentLoaded', (event) => {
const previousTimestamp = previousMessage ? new Date(previousMessage.timestamp).getTime() : 0; const previousTimestamp = previousMessage ? new Date(previousMessage.timestamp).getTime() : 0;
const showUsername = index === 0 || msg.sender !== previousMessage.sender || shouldShowUsername(previousTimestamp, currentTimestamp); const showUsername = index === 0 || msg.sender !== previousMessage.sender || shouldShowUsername(previousTimestamp, currentTimestamp);
if (showUsername) { const profilePicUrl = msg.sender_profile_picture ? `${msg.sender_profile_picture}` : 'default_profile_picture.png';
const usernameElement = document.createElement('div'); appendMessage(messagesList, msg, showUsername, profilePicUrl);
usernameElement.classList.add('message-sender');
if (msg.sender === username) {
usernameElement.classList.add('message-myself-sender');
}
const profilePictureUrl = `/cdn/${msg.sender_profile_picture}`;
usernameElement.innerHTML = `<img src="${profilePictureUrl}" alt="${msg.sender}" class="profile-picture enhanceable-image"> <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') {
messageElement.innerHTML = `${md.render(msg.content)}<div class="timestamp">${msg.timestamp}</div>`;
} 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 = `<img src="/cdn/${msg.content}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${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">${msg.timestamp}</div>`;
} else {
messageElement.innerHTML = `<a href="/cdn/${msg.content}" target="_blank">Download File</a><div class="timestamp">${msg.timestamp}</div>`;
}
}
messagesList.appendChild(messageElement);
}); });
scrollToBottom(); scrollToBottom(messagesList);
}) })
.catch(error => { .catch(error => console.error('Error fetching group messages:', error));
console.error('Error fetching group messages:', error);
alert('Error fetching group messages: ' + error.message);
});
} }
const sendMessageForm = document.querySelector('.send-message-form'); const sendMessageForm = document.querySelector('.send-message-form');
@ -187,7 +82,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
formData.append('file', fileInput.files[0]); formData.append('file', fileInput.files[0]);
} }
fetch(`/send_group_message/${groupId}`, { fetch(`/send_group_message/${receiver}`, {
method: 'POST', method: 'POST',
body: formData body: formData
}).then(response => response.json()) }).then(response => response.json())
@ -202,97 +97,38 @@ document.addEventListener('DOMContentLoaded', (event) => {
document.querySelector('.file-preview .file-preview-filename').style.display = 'none'; document.querySelector('.file-preview .file-preview-filename').style.display = 'none';
document.querySelector('.file-preview .file-preview-filename').textContent = ''; document.querySelector('.file-preview .file-preview-filename').textContent = '';
// The message will be appended only when the server confirms it // Append the message immediately for the sender
socket.emit('new_group_message', { const profilePictureUrl = profilePicture ? `${profilePicture}` : 'default_profile_picture.png';
group_id: groupId, const messageData = {
sender: username, sender: username,
content: content, content: content,
content_type: 'text', // Adjust this as necessary
timestamp: timestamp, timestamp: timestamp,
sender_profile_picture: profilePictureUrl,
content_type: fileInput.files.length > 0 ? 'file' : 'text',
id: data.message_id 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 { } else {
showToast('Message sending failed'); showToast('Message sending failed');
} }
}).catch(error => { }).catch(error => {
showToast('Message sending failed'); showToast('Message sending failed');
console.error('Error sending group message:', error); console.error('Error sending message:', error);
}); });
} }
}); });
// Handle Shift + Enter for new line document.querySelector('input[name="file"]').addEventListener('change', handleFileInputChange);
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', function(event) { document.querySelector('.remove-file').addEventListener('click', handleRemoveFile);
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) { document.addEventListener('click', function (event) {
if (event.target.classList.contains('enhanceable-image')) { if (event.target.classList.contains('enhanceable-image')) {
overlayImage.src = event.target.src; overlayImage.src = event.target.src;
@ -303,46 +139,4 @@ document.addEventListener('DOMContentLoaded', (event) => {
imageOverlay.addEventListener('click', function () { imageOverlay.addEventListener('click', function () {
imageOverlay.style.display = 'none'; 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';
});
}); });

View file

@ -1,25 +1,58 @@
document.addEventListener('DOMContentLoaded', (event) => { document.addEventListener('DOMContentLoaded', (event) => {
function requestNotificationPermission() { const socket = io();
if (Notification.permission !== "granted") {
Notification.requestPermission(); let isConnected = true;
}
function checkConnection() {
socket.emit('ping');
} }
function showNotification(title, body) { socket.on('pong', function () {
if (Notification.permission === "granted") { isConnected = true;
new Notification(title, { body }); });
}
} setInterval(() => {
if (!isConnected) {
requestNotificationPermission(); window.location.reload();
}
function handleMessageNotification(data) { isConnected = false;
const title = `New Message from ${data.sender}`; checkConnection();
const body = data.content.length > 100 ? data.content.substring(0, 97) + '...' : data.content; }, 3000);
showNotification(title, body);
} socket.on('connect', () => {
isConnected = true;
window.notificationHandler = { });
handleMessageNotification
}; 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 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}`);
}
});
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}`);
}
});
}); });

View file

@ -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 = '';
}

View file

@ -640,6 +640,10 @@ video {
display: table; display: table;
} }
.hidden {
display: none;
}
.\!h-\[400px\] { .\!h-\[400px\] {
height: 400px !important; height: 400px !important;
} }

View file

@ -1,16 +1,19 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% endblock %}</title> <title>{% block title %}{% endblock %}</title>
<script src="https://cdn.jsdelivr.net/npm/markdown-it/dist/markdown-it.min.js"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}"> <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='output.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='output.css') }}">
<script src="{{ url_for('static', filename='js/notification.js') }}"></script> <script src="{{ url_for('static', filename='js/notification.js') }}"></script>
<script src="{{ url_for('static', filename='js/common.js') }}"></script>
<script src="{{ url_for('static', filename='js/upload_handler.js') }}"></script>
<script src="https://cdn.socket.io/4.0.1/socket.io.min.js"></script> <script src="https://cdn.socket.io/4.0.1/socket.io.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/markdown-it/dist/markdown-it.min.js"></script>
<script src="https://vjs.zencdn.net/7.11.4/video.min.js"></script> <script src="https://vjs.zencdn.net/7.11.4/video.min.js"></script>
<script src="https://kit.fontawesome.com/47dbf3c43e.js" crossorigin="anonymous"></script> <script src="https://kit.fontawesome.com/47dbf3c43e.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="{{ url_for('static', filename='js/connection_check.js') }}"></script> <input type="hidden" id="current-chat-user-id" value="{{ friend.id if friend else '' }}">
<input type="hidden" id="current-group-id" value="{{ group.id if group else '' }}">
<script> <script>
// Function to apply the theme based on user selection or system preference // Function to apply the theme based on user selection or system preference