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
|
*.db
|
||||||
secret.key
|
*.key
|
||||||
|
secret_key.json
|
||||||
app.log
|
app.log
|
||||||
/cdn
|
/cdn
|
||||||
/uploads
|
/uploads
|
||||||
|
|
119
app.py
119
app.py
|
@ -6,6 +6,7 @@ import hashlib
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
import json
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
from flask_socketio import SocketIO, emit, join_room, leave_room
|
from flask_socketio import SocketIO, emit, join_room, leave_room
|
||||||
|
@ -19,8 +20,21 @@ logging.basicConfig(filename='app.log', level=logging.DEBUG,
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config['SECRET_KEY'] = os.urandom(24)
|
app.config['SECRET_KEY'] = None # Initially set to None
|
||||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
|
|
||||||
|
def load_secret_key():
|
||||||
|
secret_key_path = 'secret_key.json'
|
||||||
|
if not os.path.exists(secret_key_path):
|
||||||
|
secret_key = os.urandom(24)
|
||||||
|
with open(secret_key_path, 'w') as key_file:
|
||||||
|
json.dump({'SECRET_KEY': secret_key.hex()}, key_file)
|
||||||
|
else:
|
||||||
|
with open(secret_key_path, 'r') as key_file:
|
||||||
|
secret_key = bytes.fromhex(json.load(key_file)['SECRET_KEY'])
|
||||||
|
return secret_key
|
||||||
|
|
||||||
|
app.config['SECRET_KEY'] = load_secret_key()
|
||||||
|
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///W:/site.db'
|
||||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||||
app.config['UPLOAD_FOLDER'] = 'cdn'
|
app.config['UPLOAD_FOLDER'] = 'cdn'
|
||||||
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
||||||
|
@ -30,12 +44,12 @@ migrate = Migrate(app, db)
|
||||||
|
|
||||||
# Load encryption key
|
# Load encryption key
|
||||||
def load_key():
|
def load_key():
|
||||||
return open("secret.key", "rb").read()
|
return open(r"W:\secret.key", "rb").read()
|
||||||
|
|
||||||
# Ensure the key file exists
|
# Ensure the key file exists
|
||||||
if not os.path.exists("secret.key"):
|
if not os.path.exists(r"W:\secret.key"):
|
||||||
key = Fernet.generate_key()
|
key = Fernet.generate_key()
|
||||||
with open("secret.key", "wb") as key_file:
|
with open(r"secret.key", "wb") as key_file:
|
||||||
key_file.write(key)
|
key_file.write(key)
|
||||||
|
|
||||||
key = load_key()
|
key = load_key()
|
||||||
|
@ -149,17 +163,31 @@ def register():
|
||||||
username = request.form['username'].lower()
|
username = request.form['username'].lower()
|
||||||
password = request.form['password']
|
password = request.form['password']
|
||||||
hashed_password = sha256_password_hash(password)
|
hashed_password = sha256_password_hash(password)
|
||||||
new_user = User(username=username, password=hashed_password)
|
profile_picture = "default_profile_picture.png" # Set default profile picture
|
||||||
|
new_user = User(username=username, password=hashed_password, profile_picture=profile_picture)
|
||||||
try:
|
try:
|
||||||
db.session.add(new_user)
|
db.session.add(new_user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
logger.info(f"New user registered: {username}")
|
logger.info(f"New user registered: {username}")
|
||||||
return redirect(url_for('login'))
|
|
||||||
|
# Automatically log the user in after registration
|
||||||
|
session['username'] = new_user.username
|
||||||
|
session['user_id'] = new_user.id
|
||||||
|
session['is_admin'] = new_user.is_admin
|
||||||
|
|
||||||
|
token = generate_token(new_user.id)
|
||||||
|
response = make_response(redirect(url_for('dashboard')))
|
||||||
|
expires = datetime.now(timezone.utc) + timedelta(days=7)
|
||||||
|
response.set_cookie('token', token, httponly=True, expires=expires)
|
||||||
|
|
||||||
|
logger.info(f"User logged in: {username} after registration")
|
||||||
|
return response
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error registering user {username}: {e}")
|
logger.error(f"Error registering user {username}: {e}")
|
||||||
return 'Username already exists!'
|
return 'Username already exists!'
|
||||||
return render_template('register.html')
|
return render_template('register.html')
|
||||||
|
|
||||||
|
|
||||||
@app.route('/login', methods=['GET', 'POST'])
|
@app.route('/login', methods=['GET', 'POST'])
|
||||||
def login():
|
def login():
|
||||||
if 'username' in session:
|
if 'username' in session:
|
||||||
|
@ -300,8 +328,12 @@ def dashboard():
|
||||||
except InvalidToken:
|
except InvalidToken:
|
||||||
decrypted_messages.append((msg.sender, "Invalid encrypted message", msg.timestamp))
|
decrypted_messages.append((msg.sender, "Invalid encrypted message", msg.timestamp))
|
||||||
|
|
||||||
|
profile_picture = user.profile_picture if user.profile_picture else 'default_profile_picture.png'
|
||||||
|
for friend in friend_users:
|
||||||
|
friend.profile_picture = friend.profile_picture if friend.profile_picture else 'default_profile_picture.png'
|
||||||
|
|
||||||
logger.info(f"User {session['username']} accessed dashboard")
|
logger.info(f"User {session['username']} accessed dashboard")
|
||||||
return render_template('dashboard.html', username=session['username'], user_id=user.id, friends=friend_users, friend_requests=friend_requests, pending_messages=decrypted_pending_messages, messages=decrypted_messages, groups=groups, is_admin=user.is_admin)
|
return render_template('dashboard.html', username=session['username'], user_id=user.id, friends=friend_users, friend_requests=friend_requests, pending_messages=decrypted_pending_messages, messages=decrypted_messages, groups=groups, is_admin=user.is_admin, profile_picture=profile_picture)
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
@app.route('/chat/<int:friend_id>')
|
@app.route('/chat/<int:friend_id>')
|
||||||
|
@ -401,7 +433,7 @@ def get_messages(friend_id):
|
||||||
{
|
{
|
||||||
'id': msg.id,
|
'id': msg.id,
|
||||||
'sender': msg.sender.username,
|
'sender': msg.sender.username,
|
||||||
'sender_profile_picture': f'/cdn/{msg.sender.profile_picture}' if msg.sender.profile_picture else None,
|
'sender_profile_picture': f'{msg.sender.profile_picture}' if msg.sender.profile_picture else None,
|
||||||
'content': cipher.decrypt(msg.content.encode()).decode() if msg.content_type == 'text' else msg.content,
|
'content': cipher.decrypt(msg.content.encode()).decode() if msg.content_type == 'text' else msg.content,
|
||||||
'content_type': msg.content_type,
|
'content_type': msg.content_type,
|
||||||
'timestamp': msg.timestamp.strftime("%Y-%m-%d %H:%M:%S")
|
'timestamp': msg.timestamp.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
@ -782,7 +814,7 @@ def handle_connect():
|
||||||
user = User.query.filter_by(username=session['username']).first()
|
user = User.query.filter_by(username=session['username']).first()
|
||||||
if user:
|
if user:
|
||||||
user.online = True
|
user.online = True
|
||||||
user.last_activity = datetime.utcnow()
|
user.last_activity = datetime.now(timezone.utc)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
emit('user_online', {'username': user.username}, broadcast=True)
|
emit('user_online', {'username': user.username}, broadcast=True)
|
||||||
logger.info(f"User {session['username']} connected")
|
logger.info(f"User {session['username']} connected")
|
||||||
|
@ -797,14 +829,60 @@ def handle_disconnect():
|
||||||
emit('user_offline', {'username': user.username}, broadcast=True)
|
emit('user_offline', {'username': user.username}, broadcast=True)
|
||||||
logger.info(f"User {session['username']} disconnected")
|
logger.info(f"User {session['username']} disconnected")
|
||||||
|
|
||||||
|
@socketio.on('send_message')
|
||||||
|
def handle_send_message(data):
|
||||||
|
sender = session['username']
|
||||||
|
receiver_id = data['receiver_id']
|
||||||
|
message = data['message']
|
||||||
|
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
# Save the message in the database
|
||||||
|
new_message = Message(sender_id=User.query.filter_by(username=sender).first().id, receiver_id=receiver_id, content=message, timestamp=timestamp)
|
||||||
|
db.session.add(new_message)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Emit the message to the receiver if they are online and not in the same chat
|
||||||
|
receiver = User.query.get(receiver_id)
|
||||||
|
if receiver.online:
|
||||||
|
emit('new_message', {
|
||||||
|
'sender': sender,
|
||||||
|
'message': message,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'receiver_id': receiver_id
|
||||||
|
}, room=receiver.username)
|
||||||
|
|
||||||
|
@socketio.on('send_group_message')
|
||||||
|
def handle_send_group_message(data):
|
||||||
|
sender = session['username']
|
||||||
|
group_id = data['group_id']
|
||||||
|
message = data['message']
|
||||||
|
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
# Save the message in the database
|
||||||
|
new_group_message = GroupMessage(sender_id=User.query.filter_by(username=sender).first().id, group_id=group_id, content=message, timestamp=timestamp)
|
||||||
|
db.session.add(new_group_message)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Emit the message to the group if they are online and not in the same group chat
|
||||||
|
group_members = GroupMember.query.filter_by(group_id=group_id).all()
|
||||||
|
for member in group_members:
|
||||||
|
user = User.query.get(member.user_id)
|
||||||
|
if user.username != sender and user.online:
|
||||||
|
emit('new_group_message', {
|
||||||
|
'sender': sender,
|
||||||
|
'message': message,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'group_id': group_id
|
||||||
|
}, room=user.username)
|
||||||
|
|
||||||
@socketio.on('join')
|
@socketio.on('join')
|
||||||
def handle_join(data):
|
def on_join(data):
|
||||||
username = data['username']
|
username = data['username']
|
||||||
join_room(username)
|
join_room(username)
|
||||||
logger.info(f"User {username} joined room {username}")
|
logger.info(f"User {username} joined room {username}")
|
||||||
|
|
||||||
@socketio.on('leave')
|
@socketio.on('leave')
|
||||||
def handle_leave(data):
|
def on_leave(data):
|
||||||
username = data['username']
|
username = data['username']
|
||||||
leave_room(username)
|
leave_room(username)
|
||||||
logger.info(f"User {username} left room {username}")
|
logger.info(f"User {username} left room {username}")
|
||||||
|
@ -823,23 +901,6 @@ def handle_leave_group(data):
|
||||||
leave_room(f"group_{group_id}")
|
leave_room(f"group_{group_id}")
|
||||||
logger.info(f"User {username} left group room {group_id}")
|
logger.info(f"User {username} left group room {group_id}")
|
||||||
|
|
||||||
@socketio.on('user_activity')
|
|
||||||
def handle_user_activity():
|
|
||||||
if 'username' in session:
|
|
||||||
user = User.query.filter_by(username=session['username']).first()
|
|
||||||
if user:
|
|
||||||
user.last_activity = datetime.utcnow()
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
def set_users_to_idle():
|
|
||||||
with app.app_context():
|
|
||||||
idle_threshold = datetime.utcnow() - timedelta(minutes=5)
|
|
||||||
users = User.query.filter(User.online == True, User.last_activity < idle_threshold).all()
|
|
||||||
for user in users:
|
|
||||||
user.online = False
|
|
||||||
db.session.commit()
|
|
||||||
emit('user_idle', {'username': user.username}, broadcast=True)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
db.create_all()
|
db.create_all()
|
||||||
|
|
BIN
instance/site.db.lnk
Normal file
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-SQLAlchemy==3.1.1
|
||||||
Flask-SocketIO==5.3.6
|
Flask-SocketIO==5.3.6
|
||||||
Flask-Migrate==4.0.7
|
Flask-Migrate==4.0.7
|
||||||
|
Flask-Session==0.8.0
|
||||||
cryptography==42.0.8
|
cryptography==42.0.8
|
||||||
Werkzeug==3.0.3
|
Werkzeug==3.0.3
|
||||||
python-socketio==5.11.2
|
python-socketio==5.11.2
|
||||||
python-engineio==4.9.1
|
python-engineio==4.9.1
|
||||||
PyJWT==2.8.0
|
PyJWT==2.8.0
|
||||||
Pillow==10.3.0
|
Pillow==10.3.0
|
||||||
|
redis==5.0.6
|
|
@ -1,21 +1,5 @@
|
||||||
document.addEventListener('DOMContentLoaded', (event) => {
|
document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
const socket = io();
|
const socket = io();
|
||||||
const md = window.markdownit();
|
|
||||||
|
|
||||||
function requestNotificationPermission() {
|
|
||||||
if (Notification.permission !== "granted") {
|
|
||||||
Notification.requestPermission();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showNotification(title, body) {
|
|
||||||
if (Notification.permission === "granted") {
|
|
||||||
new Notification(title, { body });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
requestNotificationPermission();
|
|
||||||
|
|
||||||
socket.emit('join', { username: username });
|
socket.emit('join', { username: username });
|
||||||
|
|
||||||
document.title = `${friendUsername}`;
|
document.title = `${friendUsername}`;
|
||||||
|
@ -23,352 +7,9 @@ document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
const messagesList = document.getElementById('messages');
|
const messagesList = document.getElementById('messages');
|
||||||
const imageOverlay = document.getElementById('image-overlay');
|
const imageOverlay = document.getElementById('image-overlay');
|
||||||
const overlayImage = document.getElementById('overlay-image');
|
const overlayImage = document.getElementById('overlay-image');
|
||||||
const contextMenu = document.getElementById('context-menu');
|
|
||||||
const toast = document.getElementById('toast');
|
|
||||||
const toastMessage = document.getElementById('toast-message');
|
|
||||||
let currentMessageId = null;
|
|
||||||
let currentMessageText = null;
|
|
||||||
|
|
||||||
function showToast(message) {
|
|
||||||
toastMessage.textContent = message;
|
|
||||||
toast.classList.add('show');
|
|
||||||
setTimeout(() => {
|
|
||||||
toast.classList.remove('show');
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollToBottom() {
|
|
||||||
messagesList.scrollTop = messagesList.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldShowUsername(previousTimestamp, currentTimestamp) {
|
|
||||||
const tenMinutes = 10 * 60 * 1000;
|
|
||||||
return (currentTimestamp - previousTimestamp) > tenMinutes;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatLocalTime(utcTimestamp) {
|
|
||||||
const date = new Date(utcTimestamp);
|
|
||||||
const localDate = new Date(date.getTime() - date.getTimezoneOffset() * 60000);
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
||||||
const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
|
|
||||||
|
|
||||||
const timeFormatter = { hour: '2-digit', minute: '2-digit' };
|
|
||||||
const dateFormatter = { year: 'numeric', month: '2-digit', day: '2-digit' };
|
|
||||||
|
|
||||||
let formattedTime;
|
|
||||||
if (localDate >= today) {
|
|
||||||
formattedTime = `Today at ${localDate.toLocaleTimeString(undefined, timeFormatter)}`;
|
|
||||||
} else if (localDate >= yesterday) {
|
|
||||||
formattedTime = `Yesterday at ${localDate.toLocaleTimeString(undefined, timeFormatter)}`;
|
|
||||||
} else {
|
|
||||||
formattedTime = `${localDate.toLocaleDateString(undefined, dateFormatter)} at ${localDate.toLocaleTimeString(undefined, timeFormatter)}`;
|
|
||||||
}
|
|
||||||
return formattedTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isImageUrl(url) {
|
|
||||||
return /\.(jpg|jpeg|png|gif|bmp|svg)$/i.test(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
function appendMessage(msg, showUsername, senderProfilePicture) {
|
|
||||||
const currentTimestamp = new Date(msg.timestamp).getTime();
|
|
||||||
previousSender = msg.sender;
|
|
||||||
previousTimestamp = currentTimestamp;
|
|
||||||
|
|
||||||
const newMessage = document.createElement('div');
|
|
||||||
newMessage.classList.add('message');
|
|
||||||
|
|
||||||
if (showUsername) {
|
|
||||||
const usernameElement = document.createElement('div');
|
|
||||||
usernameElement.classList.add('message-sender');
|
|
||||||
if (msg.sender === username) {
|
|
||||||
usernameElement.classList.add('message-myself-sender');
|
|
||||||
}
|
|
||||||
usernameElement.innerHTML = `
|
|
||||||
<img src="/cdn/${senderProfilePicture}" alt="/cdn/${msg.sender}" class="profile-picture">
|
|
||||||
<strong>${msg.sender}</strong>
|
|
||||||
<div class="message-sender-timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
|
||||||
messagesList.appendChild(usernameElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.content_type === 'text') {
|
|
||||||
if (isImageUrl(msg.content)) {
|
|
||||||
newMessage.innerHTML = `<img src="${msg.content}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
|
||||||
} else {
|
|
||||||
newMessage.innerHTML = `${md.render(msg.content)}<div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
|
||||||
}
|
|
||||||
} else if (msg.content_type === 'file') {
|
|
||||||
const isImage = isImageUrl(msg.content);
|
|
||||||
const isVideo = /\.(mp4|webm|ogg)$/i.test(msg.content);
|
|
||||||
if (isImage) {
|
|
||||||
newMessage.innerHTML = `<img src="/cdn/${msg.content}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
|
||||||
} else if (isVideo) {
|
|
||||||
newMessage.innerHTML = `<video controls style="max-width: 200px; max-height: 200px;"><source src="/cdn/${msg.content}" type="video/mp4"></video><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
|
||||||
} else {
|
|
||||||
newMessage.innerHTML = `<a href="/cdn/${msg.content}" target="_blank">Download File</a><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
messagesList.appendChild(newMessage);
|
|
||||||
scrollToBottom();
|
|
||||||
}
|
|
||||||
|
|
||||||
let previousSender = null;
|
let previousSender = null;
|
||||||
let previousTimestamp = 0;
|
let previousTimestamp = 0;
|
||||||
|
|
||||||
socket.on('new_message', function(data) {
|
|
||||||
// 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() {
|
function checkFriendRequests() {
|
||||||
const friendRequestsList = document.getElementById('friend-requests');
|
const friendRequestsList = document.getElementById('friend-requests');
|
||||||
const friendRequestsSection = document.getElementById('friend-requests-section');
|
const friendRequestsSection = document.getElementById('friend-requests-section');
|
||||||
|
@ -383,57 +24,115 @@ document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
|
|
||||||
checkFriendRequests();
|
checkFriendRequests();
|
||||||
|
|
||||||
// Image enhancer functionality
|
socket.on('new_message', function (data) {
|
||||||
document.addEventListener('click', function(event) {
|
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')) {
|
if (event.target.classList.contains('enhanceable-image')) {
|
||||||
overlayImage.src = event.target.src;
|
overlayImage.src = event.target.src;
|
||||||
imageOverlay.style.display = 'flex';
|
imageOverlay.style.display = 'flex';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
imageOverlay.addEventListener('click', function() {
|
imageOverlay.addEventListener('click', function () {
|
||||||
imageOverlay.style.display = 'none';
|
imageOverlay.style.display = 'none';
|
||||||
});
|
});
|
||||||
|
|
||||||
// Custom Context Menu Functionality
|
|
||||||
document.addEventListener('contextmenu', function(event) {
|
|
||||||
const messageElement = event.target.closest('.message');
|
|
||||||
if (messageElement) {
|
|
||||||
event.preventDefault();
|
|
||||||
currentMessageId = messageElement.dataset.messageId;
|
|
||||||
currentMessageText = messageElement.dataset.messageText;
|
|
||||||
|
|
||||||
contextMenu.style.top = `${event.clientY}px`;
|
|
||||||
contextMenu.style.left = `${event.clientX}px`;
|
|
||||||
contextMenu.style.display = 'block';
|
|
||||||
} else {
|
|
||||||
contextMenu.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('copy-text').addEventListener('click', function() {
|
|
||||||
if (currentMessageText) {
|
|
||||||
navigator.clipboard.writeText(currentMessageText).then(() => {
|
|
||||||
showToast('Text copied to clipboard');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
contextMenu.style.display = 'none';
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('copy-id').addEventListener('click', function() {
|
|
||||||
if (currentMessageId) {
|
|
||||||
navigator.clipboard.writeText(currentMessageId).then(() => {
|
|
||||||
showToast('Message ID copied to clipboard');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
contextMenu.style.display = 'none';
|
|
||||||
});
|
|
||||||
|
|
||||||
contextMenu.addEventListener('contextmenu', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('click', function() {
|
|
||||||
contextMenu.style.display = 'none';
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
136
static/js/common.js
Normal file
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(() => {
|
setInterval(() => {
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
if (confirm('Something went wrong, please reload the website')) {
|
window.location.reload();
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
isConnected = false;
|
isConnected = false;
|
||||||
checkConnection();
|
checkConnection();
|
||||||
|
@ -26,8 +24,6 @@ document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', () => {
|
||||||
isConnected = false;
|
isConnected = false;
|
||||||
if (confirm('Something went wrong, please reload the website')) {
|
window.location.reload();
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
document.addEventListener('DOMContentLoaded', (event) => {
|
document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
const socket = io();
|
const socket = io();
|
||||||
const md = window.markdownit();
|
|
||||||
socket.emit('join_group', { group_id: groupId });
|
socket.emit('join_group', { group_id: groupId });
|
||||||
|
|
||||||
document.title = `${groupName}`;
|
document.title = `${groupName}`;
|
||||||
|
@ -9,166 +8,62 @@ document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
const imageOverlay = document.getElementById('image-overlay');
|
const imageOverlay = document.getElementById('image-overlay');
|
||||||
const overlayImage = document.getElementById('overlay-image');
|
const overlayImage = document.getElementById('overlay-image');
|
||||||
const contextMenu = document.getElementById('context-menu');
|
const contextMenu = document.getElementById('context-menu');
|
||||||
const toast = document.getElementById('toast');
|
|
||||||
const toastMessage = document.getElementById('toast-message');
|
|
||||||
let currentMessageId = null;
|
let currentMessageId = null;
|
||||||
let currentMessageText = null;
|
let currentMessageText = null;
|
||||||
|
|
||||||
function showToast(message) {
|
|
||||||
toastMessage.textContent = message;
|
|
||||||
toast.classList.add('show');
|
|
||||||
setTimeout(() => {
|
|
||||||
toast.classList.remove('show');
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollToBottom() {
|
|
||||||
messagesList.scrollTop = messagesList.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldShowUsername(previousTimestamp, currentTimestamp) {
|
|
||||||
const tenMinutes = 10 * 60 * 1000;
|
|
||||||
return (currentTimestamp - previousTimestamp) > tenMinutes;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatLocalTime(utcTimestamp) {
|
|
||||||
const date = new Date(utcTimestamp);
|
|
||||||
const localDate = new Date(date.getTime() - date.getTimezoneOffset() * 60000);
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
||||||
const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
|
|
||||||
|
|
||||||
const timeFormatter = { hour: '2-digit', minute: '2-digit' };
|
|
||||||
const dateFormatter = { year: 'numeric', month: '2-digit', day: '2-digit' };
|
|
||||||
|
|
||||||
let formattedTime;
|
|
||||||
if (localDate >= today) {
|
|
||||||
formattedTime = `Today at ${localDate.toLocaleTimeString(undefined, timeFormatter)}`;
|
|
||||||
} else if (localDate >= yesterday) {
|
|
||||||
formattedTime = `Yesterday at ${localDate.toLocaleTimeString(undefined, timeFormatter)}`;
|
|
||||||
} else {
|
|
||||||
formattedTime = `${localDate.toLocaleDateString(undefined, dateFormatter)} at ${localDate.toLocaleTimeString(undefined, timeFormatter)}`;
|
|
||||||
}
|
|
||||||
return formattedTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isImageUrl(url) {
|
|
||||||
return /\.(jpg|jpeg|png|gif|bmp|svg)$/i.test(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
let previousSender = null;
|
let previousSender = null;
|
||||||
let previousTimestamp = 0;
|
let previousTimestamp = 0;
|
||||||
|
|
||||||
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 currentTimestamp = new Date(data.timestamp).getTime();
|
||||||
const showUsername = previousSender !== data.sender || shouldShowUsername(previousTimestamp, currentTimestamp);
|
const showUsername = previousSender !== data.sender || shouldShowUsername(previousTimestamp, currentTimestamp);
|
||||||
previousSender = data.sender;
|
previousSender = data.sender;
|
||||||
previousTimestamp = currentTimestamp;
|
previousTimestamp = currentTimestamp;
|
||||||
|
|
||||||
const newMessage = document.createElement('div');
|
const profilePicUrl = data.sender_profile_picture ? `${data.sender_profile_picture}` : 'default_profile_picture.png';
|
||||||
newMessage.classList.add('message');
|
appendMessage(messagesList, data, showUsername, profilePicUrl);
|
||||||
|
|
||||||
newMessage.dataset.messageId = data.id;
|
|
||||||
newMessage.dataset.messageText = data.content;
|
|
||||||
|
|
||||||
if (showUsername) {
|
|
||||||
const usernameElement = document.createElement('div');
|
|
||||||
usernameElement.classList.add('message-sender');
|
|
||||||
const profilePictureUrl = `/cdn/${data.sender_profile_picture}`;
|
|
||||||
usernameElement.innerHTML = `<img src="${profilePictureUrl}" alt="${data.sender}" class="profile-picture enhanceable-image"> <strong>${data.sender}</strong><div class="message-sender-timestamp">${formatLocalTime(data.timestamp)}</div>`;
|
|
||||||
messagesList.appendChild(usernameElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.content_type === 'text') {
|
|
||||||
if (isImageUrl(data.content)) {
|
|
||||||
newMessage.innerHTML = `<img src="${data.content}" alt="Image" class="enhanceable-image" style="max-width: 300px; max-height: 300px;" /><div class="timestamp">${formatLocalTime(data.timestamp)}</div>`;
|
|
||||||
} else {
|
|
||||||
newMessage.innerHTML = `${md.render(data.content)}<div class="timestamp">${formatLocalTime(data.timestamp)}</div>`;
|
|
||||||
}
|
|
||||||
} else if (data.content_type === 'file') {
|
|
||||||
const isImage = isImageUrl(data.content);
|
|
||||||
const isVideo = /\.(mp4|webm|ogg)$/i.test(data.content);
|
|
||||||
if (isImage) {
|
|
||||||
newMessage.innerHTML = `<img src="/cdn/${data.content}" alt="Image" class="enhanceable-image" style="max-width: 300px; max-height: 300px;" /><div class="timestamp">${formatLocalTime(data.timestamp)}</div>`;
|
|
||||||
} else if (isVideo) {
|
|
||||||
newMessage.innerHTML = `<video controls style="max-width: 300px; max-height: 300px;"><source src="/cdn/${data.content}" type="video/mp4"></video><div class="timestamp">${formatLocalTime(data.timestamp)}</div>`;
|
|
||||||
} else {
|
|
||||||
newMessage.innerHTML = `<a href="/cdn/${data.content}" target="_blank">Download File</a><div class="timestamp">${formatLocalTime(data.timestamp)}</div>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
messagesList.appendChild(newMessage);
|
|
||||||
scrollToBottom();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (groupId) {
|
if (groupId) {
|
||||||
fetch(`/get_group_messages/${groupId}`)
|
fetch(`/get_group_messages/${groupId}`)
|
||||||
.then(response => {
|
.then(response => response.json())
|
||||||
if (!response.ok) {
|
.then(data => {
|
||||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
messagesList.innerHTML = '';
|
||||||
}
|
document.getElementById('chat-with').textContent = `${groupName}`;
|
||||||
return response.json();
|
document.getElementById('send-message-form').dataset.receiver = groupId;
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
messagesList.innerHTML = '';
|
|
||||||
document.getElementById('chat-with').textContent = `${groupName}`;
|
|
||||||
document.getElementById('send-message-form').dataset.receiver = groupId;
|
|
||||||
|
|
||||||
data.messages.forEach((msg, index, messages) => {
|
data.messages.forEach((msg, index, messages) => {
|
||||||
const currentTimestamp = new Date(msg.timestamp).getTime();
|
const currentTimestamp = new Date(msg.timestamp).getTime();
|
||||||
const previousMessage = messages[index - 1];
|
const previousMessage = messages[index - 1];
|
||||||
const previousTimestamp = previousMessage ? new Date(previousMessage.timestamp).getTime() : 0;
|
const previousTimestamp = previousMessage ? new Date(previousMessage.timestamp).getTime() : 0;
|
||||||
const showUsername = index === 0 || msg.sender !== previousMessage.sender || shouldShowUsername(previousTimestamp, currentTimestamp);
|
const showUsername = index === 0 || msg.sender !== previousMessage.sender || shouldShowUsername(previousTimestamp, currentTimestamp);
|
||||||
|
|
||||||
if (showUsername) {
|
const profilePicUrl = msg.sender_profile_picture ? `${msg.sender_profile_picture}` : 'default_profile_picture.png';
|
||||||
const usernameElement = document.createElement('div');
|
appendMessage(messagesList, msg, showUsername, profilePicUrl);
|
||||||
usernameElement.classList.add('message-sender');
|
});
|
||||||
if (msg.sender === username) {
|
|
||||||
usernameElement.classList.add('message-myself-sender');
|
|
||||||
}
|
|
||||||
const profilePictureUrl = `/cdn/${msg.sender_profile_picture}`;
|
|
||||||
usernameElement.innerHTML = `<img src="${profilePictureUrl}" alt="${msg.sender}" class="profile-picture enhanceable-image"> <strong>${msg.sender}</strong><div class="message-sender-timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
|
||||||
messagesList.appendChild(usernameElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const messageElement = document.createElement('div');
|
|
||||||
messageElement.classList.add('message');
|
|
||||||
|
|
||||||
messageElement.dataset.messageId = msg.id;
|
|
||||||
messageElement.dataset.messageText = msg.content;
|
|
||||||
|
|
||||||
if (msg.content_type === 'text') {
|
|
||||||
messageElement.innerHTML = `${md.render(msg.content)}<div class="timestamp">${msg.timestamp}</div>`;
|
|
||||||
} else if (msg.content_type === 'file') {
|
|
||||||
const isImage = /\.(jpg|jpeg|png|gif|bmp|svg)$/i.test(msg.content);
|
|
||||||
const isVideo = /\.(mp4|webm|ogg)$/i.test(msg.content);
|
|
||||||
if (isImage) {
|
|
||||||
messageElement.innerHTML = `<img src="/cdn/${msg.content}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${msg.timestamp}</div>`;
|
|
||||||
} else if (isVideo) {
|
|
||||||
messageElement.innerHTML = `<video controls style="max-width: 200px; max-height: 200px;"><source src="/cdn/${msg.content}" type="video/mp4"></video><div class="timestamp">${msg.timestamp}</div>`;
|
|
||||||
} else {
|
|
||||||
messageElement.innerHTML = `<a href="/cdn/${msg.content}" target="_blank">Download File</a><div class="timestamp">${msg.timestamp}</div>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
messagesList.appendChild(messageElement);
|
|
||||||
});
|
|
||||||
|
|
||||||
scrollToBottom();
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error fetching group messages:', error);
|
|
||||||
alert('Error fetching group messages: ' + error.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
scrollToBottom(messagesList);
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error fetching group messages:', error));
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendMessageForm = document.querySelector('.send-message-form');
|
const sendMessageForm = document.querySelector('.send-message-form');
|
||||||
if (sendMessageForm) {
|
if (sendMessageForm) {
|
||||||
sendMessageForm.addEventListener('submit', function(event) {
|
sendMessageForm.addEventListener('submit', function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const receiver = this.dataset.receiver;
|
const receiver = this.dataset.receiver;
|
||||||
const contentInput = this.querySelector('input[name="content"]');
|
const contentInput = this.querySelector('input[name="content"]');
|
||||||
|
@ -187,162 +82,61 @@ document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
formData.append('file', fileInput.files[0]);
|
formData.append('file', fileInput.files[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(`/send_group_message/${groupId}`, {
|
fetch(`/send_group_message/${receiver}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
}).then(response => response.json())
|
}).then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.status === 'Message sent') {
|
if (data.status === 'Message sent') {
|
||||||
contentInput.value = '';
|
contentInput.value = '';
|
||||||
fileInput.value = '';
|
fileInput.value = '';
|
||||||
document.querySelector('.file-preview').style.display = 'none';
|
document.querySelector('.file-preview').style.display = 'none';
|
||||||
document.querySelector('.file-preview img').src = '';
|
document.querySelector('.file-preview img').src = '';
|
||||||
document.querySelector('.file-preview video').style.display = 'none';
|
document.querySelector('.file-preview video').style.display = 'none';
|
||||||
document.querySelector('.file-preview video').src = '';
|
document.querySelector('.file-preview video').src = '';
|
||||||
document.querySelector('.file-preview .file-preview-filename').style.display = 'none';
|
document.querySelector('.file-preview .file-preview-filename').style.display = 'none';
|
||||||
document.querySelector('.file-preview .file-preview-filename').textContent = '';
|
document.querySelector('.file-preview .file-preview-filename').textContent = '';
|
||||||
|
|
||||||
// The message will be appended only when the server confirms it
|
// Append the message immediately for the sender
|
||||||
socket.emit('new_group_message', {
|
const profilePictureUrl = profilePicture ? `${profilePicture}` : 'default_profile_picture.png';
|
||||||
group_id: groupId,
|
const messageData = {
|
||||||
sender: username,
|
sender: username,
|
||||||
content: content,
|
content: content,
|
||||||
content_type: 'text', // Adjust this as necessary
|
timestamp: timestamp,
|
||||||
timestamp: timestamp,
|
sender_profile_picture: profilePictureUrl,
|
||||||
id: data.message_id
|
content_type: fileInput.files.length > 0 ? 'file' : 'text',
|
||||||
});
|
id: data.message_id
|
||||||
} else {
|
};
|
||||||
showToast('Message sending failed');
|
|
||||||
}
|
const currentTimestamp = new Date(messageData.timestamp).getTime();
|
||||||
}).catch(error => {
|
const showUsername = previousSender !== username || shouldShowUsername(previousTimestamp, currentTimestamp);
|
||||||
showToast('Message sending failed');
|
previousSender = username;
|
||||||
console.error('Error sending group message:', error);
|
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
|
document.querySelector('input[name="file"]').addEventListener('change', handleFileInputChange);
|
||||||
const messageInput = document.querySelector('input[name="content"]');
|
|
||||||
messageInput.addEventListener('keydown', function(event) {
|
|
||||||
if (event.key === 'Enter' && event.shiftKey) {
|
|
||||||
event.preventDefault();
|
|
||||||
const start = this.selectionStart;
|
|
||||||
const end = this.selectionEnd;
|
|
||||||
this.value = this.value.substring(0, start) + "\n" + this.value.substring(end);
|
|
||||||
this.selectionStart = this.selectionEnd = start + 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelector('input[name="file"]').addEventListener('change', function(event) {
|
document.querySelector('.remove-file').addEventListener('click', handleRemoveFile);
|
||||||
const file = event.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
const imagePreview = document.querySelector('.file-preview-image');
|
|
||||||
const videoPreview = document.querySelector('.file-preview-video');
|
|
||||||
const filenamePreview = document.querySelector('.file-preview-filename');
|
|
||||||
reader.onload = function(e) {
|
|
||||||
document.querySelector('.file-preview').style.display = 'block';
|
|
||||||
if (file.type.startsWith('image/')) {
|
|
||||||
imagePreview.style.display = 'block';
|
|
||||||
videoPreview.style.display = 'none';
|
|
||||||
filenamePreview.style.display = 'none';
|
|
||||||
imagePreview.src = e.target.result;
|
|
||||||
} else if (file.type.startsWith('video/')) {
|
|
||||||
videoPreview.style.display = 'block';
|
|
||||||
imagePreview.style.display = 'none';
|
|
||||||
filenamePreview.style.display = 'none';
|
|
||||||
videoPreview.querySelector('source').src = e.target.result;
|
|
||||||
videoPreview.load();
|
|
||||||
} else {
|
|
||||||
filenamePreview.style.display = 'block';
|
|
||||||
imagePreview.style.display = 'none';
|
|
||||||
videoPreview.style.display = 'none';
|
|
||||||
filenamePreview.textContent = file.name;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelector('.remove-file').addEventListener('click', function() {
|
|
||||||
const fileInput = document.querySelector('input[name="file"]');
|
|
||||||
fileInput.value = '';
|
|
||||||
const filePreview = document.querySelector('.file-preview');
|
|
||||||
filePreview.style.display = 'none';
|
|
||||||
filePreview.querySelector('img').style.display = 'none';
|
|
||||||
filePreview.querySelector('img').src = '';
|
|
||||||
filePreview.querySelector('video').style.display = 'none';
|
|
||||||
filePreview.querySelector('video').src = '';
|
|
||||||
filePreview.querySelector('.file-preview-filename').style.display = 'none';
|
|
||||||
filePreview.querySelector('.file-preview-filename').textContent = '';
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkFriendRequests() {
|
document.addEventListener('click', function (event) {
|
||||||
const friendRequestsList = document.getElementById('friend-requests');
|
|
||||||
const friendRequestsSection = document.getElementById('friend-requests-section');
|
|
||||||
if (friendRequestsList && friendRequestsSection) {
|
|
||||||
if (friendRequestsList.children.length === 0) {
|
|
||||||
friendRequestsSection.style.display = 'none';
|
|
||||||
} else {
|
|
||||||
friendRequestsSection.style.display = 'block';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkFriendRequests();
|
|
||||||
|
|
||||||
// Image enhancer functionality
|
|
||||||
document.addEventListener('click', function(event) {
|
|
||||||
if (event.target.classList.contains('enhanceable-image')) {
|
if (event.target.classList.contains('enhanceable-image')) {
|
||||||
overlayImage.src = event.target.src;
|
overlayImage.src = event.target.src;
|
||||||
imageOverlay.style.display = 'flex';
|
imageOverlay.style.display = 'flex';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
imageOverlay.addEventListener('click', function() {
|
imageOverlay.addEventListener('click', function () {
|
||||||
imageOverlay.style.display = 'none';
|
imageOverlay.style.display = 'none';
|
||||||
});
|
});
|
||||||
|
|
||||||
// Custom Context Menu Functionality
|
|
||||||
document.addEventListener('contextmenu', function(event) {
|
|
||||||
const messageElement = event.target.closest('.message');
|
|
||||||
if (messageElement) {
|
|
||||||
event.preventDefault();
|
|
||||||
currentMessageId = messageElement.dataset.messageId;
|
|
||||||
currentMessageText = messageElement.dataset.messageText;
|
|
||||||
|
|
||||||
contextMenu.style.top = `${event.clientY}px`;
|
|
||||||
contextMenu.style.left = `${event.clientX}px`;
|
|
||||||
contextMenu.style.display = 'block';
|
|
||||||
} else {
|
|
||||||
contextMenu.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('copy-text').addEventListener('click', function() {
|
|
||||||
if (currentMessageText) {
|
|
||||||
navigator.clipboard.writeText(currentMessageText).then(() => {
|
|
||||||
showToast('Text copied to clipboard');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
contextMenu.style.display = 'none';
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('copy-id').addEventListener('click', function() {
|
|
||||||
if (currentMessageId) {
|
|
||||||
navigator.clipboard.writeText(currentMessageId).then(() => {
|
|
||||||
showToast('Message ID copied to clipboard');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
contextMenu.style.display = 'none';
|
|
||||||
});
|
|
||||||
|
|
||||||
contextMenu.addEventListener('contextmenu', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('click', function() {
|
|
||||||
contextMenu.style.display = 'none';
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,25 +1,58 @@
|
||||||
document.addEventListener('DOMContentLoaded', (event) => {
|
document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
function requestNotificationPermission() {
|
const socket = io();
|
||||||
if (Notification.permission !== "granted") {
|
|
||||||
Notification.requestPermission();
|
let isConnected = true;
|
||||||
|
|
||||||
|
function checkConnection() {
|
||||||
|
socket.emit('ping');
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
// Function to check if the user is in the same chat or group chat
|
||||||
if (Notification.permission === "granted") {
|
function isUserInSameChat(sender, receiverId, groupId) {
|
||||||
new Notification(title, { body });
|
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();
|
socket.on('new_group_message', (data) => {
|
||||||
|
const { sender, message, timestamp, group_id } = data;
|
||||||
function handleMessageNotification(data) {
|
if (!isUserInSameChat(sender, null, group_id) || !isUserFocused()) {
|
||||||
const title = `New Message from ${data.sender}`;
|
// Display notification logic here
|
||||||
const body = data.content.length > 100 ? data.content.substring(0, 97) + '...' : data.content;
|
console.log(`New group message from ${sender}: ${message}`);
|
||||||
showNotification(title, body);
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
window.notificationHandler = {
|
|
||||||
handleMessageNotification
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
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;
|
display: table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.\!h-\[400px\] {
|
.\!h-\[400px\] {
|
||||||
height: 400px !important;
|
height: 400px !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +1,46 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{% block title %}{% endblock %}</title>
|
<title>{% block title %}{% endblock %}</title>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
<script src="https://cdn.jsdelivr.net/npm/markdown-it/dist/markdown-it.min.js"></script>
|
||||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='output.css') }}">
|
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
<script src="{{ url_for('static', filename='js/notification.js') }}"></script>
|
<link rel="stylesheet" href="{{ url_for('static', filename='output.css') }}">
|
||||||
<script src="https://cdn.socket.io/4.0.1/socket.io.min.js"></script>
|
<script src="{{ url_for('static', filename='js/notification.js') }}"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/markdown-it/dist/markdown-it.min.js"></script>
|
<script src="{{ url_for('static', filename='js/common.js') }}"></script>
|
||||||
<script src="https://vjs.zencdn.net/7.11.4/video.min.js"></script>
|
<script src="{{ url_for('static', filename='js/upload_handler.js') }}"></script>
|
||||||
<script src="https://kit.fontawesome.com/47dbf3c43e.js" crossorigin="anonymous"></script>
|
<script src="https://cdn.socket.io/4.0.1/socket.io.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
<script src="https://vjs.zencdn.net/7.11.4/video.min.js"></script>
|
||||||
<script src="{{ url_for('static', filename='js/connection_check.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>
|
<script>
|
||||||
// Function to apply the theme based on user selection or system preference
|
// Function to apply the theme based on user selection or system preference
|
||||||
function applyTheme(theme) {
|
function applyTheme(theme) {
|
||||||
const root = document.documentElement;
|
const root = document.documentElement;
|
||||||
if (theme === 'dark' || (theme === 'automatic' && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
if (theme === 'dark' || (theme === 'automatic' && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||||
root.classList.add('dark');
|
root.classList.add('dark');
|
||||||
} else {
|
} else {
|
||||||
root.classList.remove('dark');
|
root.classList.remove('dark');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Apply the theme on page load based on the current theme setting
|
// Apply the theme on page load based on the current theme setting
|
||||||
document.addEventListener('DOMContentLoaded', (event) => {
|
document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
const currentTheme = localStorage.getItem('theme') || 'automatic';
|
const currentTheme = localStorage.getItem('theme') || 'automatic';
|
||||||
applyTheme(currentTheme);
|
applyTheme(currentTheme);
|
||||||
|
|
||||||
// Listen for changes to the system theme and apply if the user selected 'automatic'
|
// Listen for changes to the system theme and apply if the user selected 'automatic'
|
||||||
if (currentTheme === 'automatic') {
|
if (currentTheme === 'automatic') {
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').addListener(e => {
|
window.matchMedia('(prefers-color-scheme: dark)').addListener(e => {
|
||||||
applyTheme('automatic');
|
applyTheme('automatic');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% if 'username' in session %}
|
{% if 'username' in session %}
|
||||||
<style>
|
<style>
|
||||||
{{ current_user.custom_css | safe }}
|
{{ current_user.custom_css | safe }}
|
||||||
</style>
|
</style>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
Loading…
Reference in a new issue