Up to date Version
This commit is contained in:
parent
bc9fcda6aa
commit
2db08a537c
12 changed files with 552 additions and 779 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,5 +1,6 @@
|
|||
*.db
|
||||
secret.key
|
||||
*.key
|
||||
secret_key.json
|
||||
app.log
|
||||
/cdn
|
||||
/uploads
|
||||
|
|
119
app.py
119
app.py
|
@ -6,6 +6,7 @@ import hashlib
|
|||
import os
|
||||
import random
|
||||
import string
|
||||
import json
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from werkzeug.utils import secure_filename
|
||||
from flask_socketio import SocketIO, emit, join_room, leave_room
|
||||
|
@ -19,8 +20,21 @@ logging.basicConfig(filename='app.log', level=logging.DEBUG,
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = os.urandom(24)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
|
||||
app.config['SECRET_KEY'] = None # Initially set to None
|
||||
|
||||
def load_secret_key():
|
||||
secret_key_path = 'secret_key.json'
|
||||
if not os.path.exists(secret_key_path):
|
||||
secret_key = os.urandom(24)
|
||||
with open(secret_key_path, 'w') as key_file:
|
||||
json.dump({'SECRET_KEY': secret_key.hex()}, key_file)
|
||||
else:
|
||||
with open(secret_key_path, 'r') as key_file:
|
||||
secret_key = bytes.fromhex(json.load(key_file)['SECRET_KEY'])
|
||||
return secret_key
|
||||
|
||||
app.config['SECRET_KEY'] = load_secret_key()
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///W:/site.db'
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
app.config['UPLOAD_FOLDER'] = 'cdn'
|
||||
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
||||
|
@ -30,12 +44,12 @@ migrate = Migrate(app, db)
|
|||
|
||||
# Load encryption key
|
||||
def load_key():
|
||||
return open("secret.key", "rb").read()
|
||||
return open(r"W:\secret.key", "rb").read()
|
||||
|
||||
# Ensure the key file exists
|
||||
if not os.path.exists("secret.key"):
|
||||
if not os.path.exists(r"W:\secret.key"):
|
||||
key = Fernet.generate_key()
|
||||
with open("secret.key", "wb") as key_file:
|
||||
with open(r"secret.key", "wb") as key_file:
|
||||
key_file.write(key)
|
||||
|
||||
key = load_key()
|
||||
|
@ -149,17 +163,31 @@ def register():
|
|||
username = request.form['username'].lower()
|
||||
password = request.form['password']
|
||||
hashed_password = sha256_password_hash(password)
|
||||
new_user = User(username=username, password=hashed_password)
|
||||
profile_picture = "default_profile_picture.png" # Set default profile picture
|
||||
new_user = User(username=username, password=hashed_password, profile_picture=profile_picture)
|
||||
try:
|
||||
db.session.add(new_user)
|
||||
db.session.commit()
|
||||
logger.info(f"New user registered: {username}")
|
||||
return redirect(url_for('login'))
|
||||
|
||||
# Automatically log the user in after registration
|
||||
session['username'] = new_user.username
|
||||
session['user_id'] = new_user.id
|
||||
session['is_admin'] = new_user.is_admin
|
||||
|
||||
token = generate_token(new_user.id)
|
||||
response = make_response(redirect(url_for('dashboard')))
|
||||
expires = datetime.now(timezone.utc) + timedelta(days=7)
|
||||
response.set_cookie('token', token, httponly=True, expires=expires)
|
||||
|
||||
logger.info(f"User logged in: {username} after registration")
|
||||
return response
|
||||
except Exception as e:
|
||||
logger.error(f"Error registering user {username}: {e}")
|
||||
return 'Username already exists!'
|
||||
return render_template('register.html')
|
||||
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if 'username' in session:
|
||||
|
@ -300,8 +328,12 @@ def dashboard():
|
|||
except InvalidToken:
|
||||
decrypted_messages.append((msg.sender, "Invalid encrypted message", msg.timestamp))
|
||||
|
||||
profile_picture = user.profile_picture if user.profile_picture else 'default_profile_picture.png'
|
||||
for friend in friend_users:
|
||||
friend.profile_picture = friend.profile_picture if friend.profile_picture else 'default_profile_picture.png'
|
||||
|
||||
logger.info(f"User {session['username']} accessed dashboard")
|
||||
return render_template('dashboard.html', username=session['username'], user_id=user.id, friends=friend_users, friend_requests=friend_requests, pending_messages=decrypted_pending_messages, messages=decrypted_messages, groups=groups, is_admin=user.is_admin)
|
||||
return render_template('dashboard.html', username=session['username'], user_id=user.id, friends=friend_users, friend_requests=friend_requests, pending_messages=decrypted_pending_messages, messages=decrypted_messages, groups=groups, is_admin=user.is_admin, profile_picture=profile_picture)
|
||||
return redirect(url_for('login'))
|
||||
|
||||
@app.route('/chat/<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
BIN
instance/site.db.lnk
Normal file
Binary file not shown.
|
@ -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
|
|
@ -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
136
static/js/common.js
Normal 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';
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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';
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
44
static/js/upload_handler.js
Normal file
44
static/js/upload_handler.js
Normal 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 = '';
|
||||
}
|
|
@ -640,6 +640,10 @@ video {
|
|||
display: table;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.\!h-\[400px\] {
|
||||
height: 400px !important;
|
||||
}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
Loading…
Reference in a new issue