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
secret.key
*.key
secret_key.json
app.log
/cdn
/uploads

119
app.py
View file

@ -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/<int:friend_id>')
@ -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()

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-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
Pillow==10.3.0
redis==5.0.6

View file

@ -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 = `
<img src="/cdn/${senderProfilePicture}" alt="/cdn/${msg.sender}" class="profile-picture">
<strong>${msg.sender}</strong>
<div class="message-sender-timestamp">${formatLocalTime(msg.timestamp)}</div>`;
messagesList.appendChild(usernameElement);
}
if (msg.content_type === 'text') {
if (isImageUrl(msg.content)) {
newMessage.innerHTML = `<img src="${msg.content}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
} else {
newMessage.innerHTML = `${md.render(msg.content)}<div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
}
} else if (msg.content_type === 'file') {
const isImage = isImageUrl(msg.content);
const isVideo = /\.(mp4|webm|ogg)$/i.test(msg.content);
if (isImage) {
newMessage.innerHTML = `<img src="/cdn/${msg.content}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
} else if (isVideo) {
newMessage.innerHTML = `<video controls style="max-width: 200px; max-height: 200px;"><source src="/cdn/${msg.content}" type="video/mp4"></video><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
} else {
newMessage.innerHTML = `<a href="/cdn/${msg.content}" target="_blank">Download File</a><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
}
}
messagesList.appendChild(newMessage);
scrollToBottom();
}
let previousSender = null;
let previousTimestamp = 0;
socket.on('new_message', function(data) {
// Check if the message is for the current chat
if (friendUsername !== data.sender && friendId != data.sender_id) {
return;
}
showNotification('New Message', `You have received a new message from ${data.sender}`);
const currentTimestamp = new Date(data.timestamp).getTime();
const showUsername = previousSender !== data.sender || shouldShowUsername(previousTimestamp, currentTimestamp);
previousSender = data.sender;
previousTimestamp = currentTimestamp;
const messageElement = document.createElement('div');
messageElement.classList.add('message');
const newMessage = document.createElement('div');
newMessage.classList.add('message');
newMessage.dataset.messageId = data.id;
newMessage.dataset.messageText = data.content;
if (showUsername) {
const usernameElement = document.createElement('div');
usernameElement.classList.add('message-sender');
const timestampFormatted = formatLocalTime(data.timestamp);
// Include the profile picture if available
const profilePicture = data.sender_profile_picture ? `<img src="/cdn/${data.sender_profile_picture}" alt="${data.sender}" class="profile-picture enhanceable-image">` : '';
usernameElement.innerHTML = `${profilePicture} <strong>${data.sender}</strong><div class="message-sender-timestamp">${timestampFormatted}</div>`;
messagesList.appendChild(usernameElement);
}
if (data.content_type === 'text') {
if (isImageUrl(data.content)) {
newMessage.innerHTML = `<img src="${data.content}" alt="Image" class="enhanceable-image" style="max-width: 300px; max-height: 300px;" /><div class="timestamp">${formatLocalTime(data.timestamp)}</div>`;
} else {
newMessage.innerHTML = `${md.render(data.content)}<div class="timestamp">${formatLocalTime(data.timestamp)}</div>`;
}
} else if (data.content_type === 'file') {
const isImage = isImageUrl(data.content);
const isVideo = /\.(mp4|webm|ogg)$/i.test(data.content);
if (isImage) {
newMessage.innerHTML = `<img src="/cdn/${data.content}" alt="Image" class="enhanceable-image" style="max-width: 300px; max-height: 300px;" /><div class="timestamp">${formatLocalTime(data.timestamp)}</div>`;
} else if (isVideo) {
newMessage.innerHTML = `<video controls style="max-width: 300px; max-height: 300px;"><source src="/cdn/${data.content}" type="video/mp4"></video><div class="timestamp">${formatLocalTime(data.timestamp)}</div>`;
} else {
newMessage.innerHTML = `<a href="/cdn/${data.content}" target="_blank">Download File</a><div class="timestamp">${formatLocalTime(data.timestamp)}</div>`;
}
}
messagesList.appendChild(newMessage);
scrollToBottom();
});
if (friendId) {
fetch(`/get_messages/${friendId}`)
.then(response => response.json())
.then(data => {
messagesList.innerHTML = '';
document.getElementById('chat-with').textContent = `${friendUsername}`;
document.getElementById('send-message-form').dataset.receiver = friendId;
data.messages.forEach((msg, index, messages) => {
const currentTimestamp = new Date(msg.timestamp).getTime();
const previousMessage = messages[index - 1];
const previousTimestamp = previousMessage ? new Date(previousMessage.timestamp).getTime() : 0;
const showUsername = index === 0 || msg.sender !== previousMessage.sender || shouldShowUsername(previousTimestamp, currentTimestamp);
if (showUsername) {
const usernameElement = document.createElement('div');
usernameElement.classList.add('message-sender');
if (msg.sender === username) {
usernameElement.classList.add('message-myself-sender');
}
// Include the profile picture if available
const profilePicture = msg.sender_profile_picture ? `<img src="${msg.sender_profile_picture}" alt="${msg.sender}" class="profile-picture enhanceable-image">` : '';
usernameElement.innerHTML = `${profilePicture} <strong>${msg.sender}</strong><div class="message-sender-timestamp">${formatLocalTime(msg.timestamp)}</div>`;
messagesList.appendChild(usernameElement);
}
const messageElement = document.createElement('div');
messageElement.classList.add('message');
messageElement.dataset.messageId = msg.id;
messageElement.dataset.messageText = msg.content;
if (msg.content_type === 'text') {
if (isImageUrl(msg.content)) {
messageElement.innerHTML = `<img src="${msg.content}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
} else {
messageElement.innerHTML = `${md.render(msg.content)}<div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
}
} else if (msg.content_type === 'file') {
const isImage = isImageUrl(msg.content);
const isVideo = /\.(mp4|webm|ogg)$/i.test(msg.content);
if (isImage) {
messageElement.innerHTML = `<img src="/cdn/${msg.content}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
} else if (isVideo) {
messageElement.innerHTML = `<video controls style="max-width: 200px; max-height: 200px;"><source src="/cdn/${msg.content}" type="video/mp4"></video><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
} else {
messageElement.innerHTML = `<a href="/cdn/${msg.content}" target="_blank">Download File</a><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
}
}
messagesList.appendChild(messageElement);
});
scrollToBottom();
})
.catch(error => console.error('Error fetching messages:', error));
}
const sendMessageForm = document.querySelector('.send-message-form');
if (sendMessageForm) {
sendMessageForm.addEventListener('submit', function(event) {
event.preventDefault();
const receiver = this.dataset.receiver;
const contentInput = this.querySelector('input[name="content"]');
const fileInput = this.querySelector('input[name="file"]');
const content = contentInput.value;
const timestamp = new Date().toISOString().slice(0, 19).replace('T', ' ');
const imagePreview = document.querySelector('.file-preview-image');
const videoPreview = document.querySelector('.file-preview-video');
const filenamePreview = document.querySelector('.file-preview-filename');
if (content || fileInput.files.length > 0) {
// Append the message locally
const newMessage = document.createElement('div');
newMessage.classList.add('message');
newMessage.dataset.messageId = 'temp-id'; // Temporary ID for the new message
const currentTimestamp = new Date(timestamp).getTime();
const showUsername = previousSender !== username || shouldShowUsername(previousTimestamp, currentTimestamp);
previousSender = username;
previousTimestamp = currentTimestamp;
if (showUsername) {
const usernameElement = document.createElement('div');
usernameElement.classList.add('message-sender');
const timestampFormatted = formatLocalTime(timestamp);
const profilePictureUrl = `/cdn/${profilePicture}`; // Correct reference
usernameElement.innerHTML = `<img src="${profilePictureUrl}" alt="${username}" class="profile-picture enhanceable-image"> <strong>${username}</strong><div class="message-sender-timestamp">${timestampFormatted}</div>`;
messagesList.appendChild(usernameElement);
}
if (content) {
newMessage.dataset.messageText = content;
if (isImageUrl(content)) {
newMessage.innerHTML = `<img src="${content}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${formatLocalTime(timestamp)}</div>`;
} else {
newMessage.innerHTML = `${md.render(content)}<div class="timestamp">${formatLocalTime(timestamp)}</div>`;
}
}
if (fileInput.files.length > 0) {
const file = fileInput.files[0];
const isImage = /\.(jpg|jpeg|png|gif|bmp|svg)$/i.test(file.name);
const isVideo = /\.(mp4|webm|ogg)$/i.test(file.name);
if (isImage) {
newMessage.innerHTML = `<img src="${imagePreview.src}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${formatLocalTime(timestamp)}</div>`;
} else if (isVideo) {
newMessage.innerHTML = `<video controls style="max-width: 200px; max-height: 200px;"><source src="${videoPreview.src}" type="video/mp4"></video><div class="timestamp">${formatLocalTime(timestamp)}</div>`;
} else {
newMessage.innerHTML = `<a href="${URL.createObjectURL(file)}" target="_blank">Download File</a><div class="timestamp">${formatLocalTime(timestamp)}</div>`;
}
}
messagesList.appendChild(newMessage);
scrollToBottom();
const formData = new FormData();
formData.append('content', content);
formData.append('timestamp', timestamp);
if (fileInput.files[0]) {
formData.append('file', fileInput.files[0]);
}
fetch(`/send_message/${receiver}`, {
method: 'POST',
body: formData
}).then(response => response.json())
.then(data => {
if (data.status === 'Message sent') {
contentInput.value = '';
fileInput.value = '';
document.querySelector('.file-preview').style.display = 'none';
document.querySelector('.file-preview img').src = '';
document.querySelector('.file-preview video').style.display = 'none';
document.querySelector('.file-preview video').src = '';
document.querySelector('.file-preview .file-preview-filename').style.display = 'none';
document.querySelector('.file-preview .file-preview-filename').textContent = '';
// Update the message ID with the real one from the server
newMessage.dataset.messageId = data.message_id;
} else {
showToast('Message sending failed');
}
}).catch(error => {
showToast('Message sending failed');
console.error('Error sending message:', error);
});
}
});
document.querySelector('input[name="file"]').addEventListener('change', function(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
const imagePreview = document.querySelector('.file-preview-image');
const videoPreview = document.querySelector('.file-preview-video');
const filenamePreview = document.querySelector('.file-preview-filename');
reader.onload = function(e) {
document.querySelector('.file-preview').style.display = 'block';
if (file.type.startsWith('image/')) {
imagePreview.style.display = 'block';
videoPreview.style.display = 'none';
filenamePreview.style.display = 'none';
imagePreview.src = e.target.result;
} else if (file.type.startsWith('video/')) {
videoPreview.style.display = 'block';
imagePreview.style.display = 'none';
filenamePreview.style.display = 'none';
videoPreview.querySelector('source').src = e.target.result;
videoPreview.load();
} else {
filenamePreview.style.display = 'block';
imagePreview.style.display = 'none';
videoPreview.style.display = 'none';
filenamePreview.textContent = file.name;
}
};
reader.readAsDataURL(file);
}
});
document.querySelector('.remove-file').addEventListener('click', function() {
const fileInput = document.querySelector('input[name="file"]');
fileInput.value = '';
const filePreview = document.querySelector('.file-preview');
filePreview.style.display = 'none';
filePreview.querySelector('img').style.display = 'none';
filePreview.querySelector('img').src = '';
filePreview.querySelector('video').style.display = 'none';
filePreview.querySelector('video').src = '';
filePreview.querySelector('.file-preview-filename').style.display = 'none';
filePreview.querySelector('.file-preview-filename').textContent = '';
});
}
function checkFriendRequests() {
const friendRequestsList = document.getElementById('friend-requests');
const friendRequestsSection = document.getElementById('friend-requests-section');
@ -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';
});
});

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,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();
});
});

View file

@ -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 = `<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();
});
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 = `<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');
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)}<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();
})
.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';
});
});

View file

@ -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}`);
}
});
});

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;
}
.hidden {
display: none;
}
.\!h-\[400px\] {
height: 400px !important;
}

View file

@ -1,43 +1,46 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='output.css') }}">
<script src="{{ url_for('static', filename='js/notification.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://kit.fontawesome.com/47dbf3c43e.js" crossorigin="anonymous"></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>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<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="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<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/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://vjs.zencdn.net/7.11.4/video.min.js"></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>
<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>
// Function to apply the theme based on user selection or system preference
function applyTheme(theme) {
const root = document.documentElement;
if (theme === 'dark' || (theme === 'automatic' && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
root.classList.add('dark');
} else {
root.classList.remove('dark');
}
<script>
// Function to apply the theme based on user selection or system preference
function applyTheme(theme) {
const root = document.documentElement;
if (theme === 'dark' || (theme === 'automatic' && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
root.classList.add('dark');
} else {
root.classList.remove('dark');
}
}
// Apply the theme on page load based on the current theme setting
document.addEventListener('DOMContentLoaded', (event) => {
const currentTheme = localStorage.getItem('theme') || 'automatic';
applyTheme(currentTheme);
// Apply the theme on page load based on the current theme setting
document.addEventListener('DOMContentLoaded', (event) => {
const currentTheme = localStorage.getItem('theme') || 'automatic';
applyTheme(currentTheme);
// 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');
});
}
});
</script>
{% if 'username' in session %}
<style>
{{ current_user.custom_css | safe }}
</style>
{% endif %}
// 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');
});
}
});
</script>
{% if 'username' in session %}
<style>
{{ current_user.custom_css | safe }}
</style>
{% endif %}