flask / templates /messages.html
hashim1's picture
Upload 18 files
5e07f18 verified
{% extends "base.html" %}
{% block title %}{{ user.username }} - الرسائل{% endblock %}
{% block content %}
<style>
/* تصميم الحاوية */
.messages-container {
max-width: 600px;
margin: 20px auto;
padding: 10px;
border: 1px solid #ddd;
border-radius: 10px;
background-color: #f9f9f9;
font-family: Arial, sans-serif;
}
/* معلومات المستخدم */
.user-info {
display: flex;
align-items: center;
margin-bottom: 15px;
padding: 10px;
background-color: #ffffff;
border-bottom: 1px solid #ddd;
}
.profile-photo {
width: 50px;
height: 50px;
border-radius: 50%;
margin-right: 10px;
}
.username {
font-size: 18px;
font-weight: bold;
}
/* قائمة الرسائل */
.messages-list {
display: flex;
flex-direction: column;
max-height: 400px;
overflow-y: auto;
padding: 10px;
background-color: #ffffff;
border-radius: 5px;
margin-bottom: 15px;
}
/* الرسائل */
.message-item {
margin: 5px 0;
padding: 10px;
border-radius: 15px;
max-width: 75%;
word-wrap: break-word;
font-size: 14px;
}
/* الرسائل المرسلة */
.message-item.sent {
background-color: #0078ff;
color: #ffffff;
align-self: flex-end;
text-align: right;
}
/* الرسائل المستلمة */
.message-item.received {
background-color: #f1f0f0;
color: #000000;
align-self: flex-start;
text-align: left;
}
/* الطابع الزمني */
.message-timestamp {
font-size: 10px;
color: #666;
margin-top: 5px;
display: block;
}
/* إدخال الرسائل */
.message-input {
/* display: flex; */
align-items: center;
gap: 10px;
}
textarea#message-input {
width: 100%;
height: 50px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
resize: none;
font-size: 14px;
}
button#send-message-btn {
background-color: #0078ff;
color: #ffffff;
border: none;
padding: 10px 15px;
border-radius: 5px;
font-size: 14px;
cursor: pointer;
transition: background-color 0.3s;
}
button#send-message-btn:hover {
background-color: #005bb5;
}
.received .seen-status{
display: none;
}
img{
max-width: 100%;
}
video{
max-width: 100%;
}
.input-group{
display: flex;
flex-direction: row-reverse;
align-items: center;
position: fixed;
bottom: 10px;
width: calc(100vw - 16.8px);
}
.file-upload-btn{
font-size: 25px;
margin-top: 5px;
}
/* دائرة التحميل */
.loading-circle {
display: inline-block;
width: 40px; /* زيادة الحجم ليكون أكثر وضوحًا */
height: 40px;
border: 4px solid #0078ff; /* اللون الأزرق الزاهي */
border-top: 4px solid transparent; /* الجزء العلوي شفاف لإعطاء التأثير الدوار */
border-radius: 50%; /* جعلها دائرية */
animation: spin 0.8s linear infinite; /* سرعة التدوير أصبحت أسرع (0.8 ثانية) */
margin: 10px auto; /* ضبط المسافة بين الدائرة والرسائل */
box-shadow: 0px 0px 10px rgba(0, 120, 255, 0.5); /* إضافة ظل خفيف لإعطاء تأثير العمق */
}
/* التأثير الذي يجعل الدائرة تدور */
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* تنسيق نافذة المعاينة المنبثقة */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 10px;
text-align: center;
position: relative;
max-width: 90%;
max-height: 90%;
margin-left: auto;
top: 50%;
margin-right: auto;
transform: translate(0, -50%);
}
#closeModal {
position: absolute;
top: 10px;
right: 10px;
background: red;
color: white;
border: none;
border-radius: 50%;
width: 30px;
height: 30px;
cursor: pointer;
}
.send-button {
background: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
margin-top: 10px;
}
.send-button:hover {
background: #0056b3;
}
#file-preview img,
#file-preview video {
max-width: 100px;
max-height: 100px;
border-radius: 5px;
margin-top: 10px;
}
/* تنسيق دائرة التحميل */
.loading-spinner {
display: flex;
justify-content: center;
align-items: center;
padding: 10px;
font-size: 14px;
color: #007bff;
background-color: #f1f1f1;
border-radius: 5px;
margin: 10px auto;
width: fit-content;
}
.loading-spinner::before {
content: "";
border: 3px solid #f3f3f3;
border-top: 3px solid #007bff;
border-radius: 50%;
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
margin-right: 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* تنسيق دائرة التحميل */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 10px;
background-color: #f1f1f1;
border-radius: 5px;
margin: 10px auto;
width: fit-content;
}
.loading-spinner {
font-size: 14px;
color: #007bff;
margin-bottom: 10px;
}
/* تنسيق شريط التقدم */
.progress-bar {
width: 200px;
height: 10px;
background-color: #e0e0e0;
border-radius: 5px;
overflow: hidden;
}
.progress {
height: 100%;
background-color: #007bff;
width: 0%;
transition: width 0.3s ease;
}
</style>
<div class="messages-container">
<!-- معلومات المستخدم -->
<div class="user-info">
<img src="{{ url_for('static', filename=user.profile_photo) }}" alt="صورة المستخدم" class="profile-photo">
<span class="username">{{ user.username }}</span>
</div>
<!-- قائمة الرسائل -->
<div class="messages-list" id="messages-list">
<div id="loading-container" style="display: none;">
<div class="loading-circle"></div>
</div>
</div>
<!-- إدخال الرسائل -->
<div class="message-input">
<form id="messageForm" enctype="multipart/form-data">
<div class="input-group">
<label for="file-input" class="file-upload-btn">
📎
<input type="file" id="file-input" name="file" accept="image/*, video/*" hidden>
</label>
<textarea id="message-input" placeholder="اكتب رسالتك هنا..."></textarea>
<button type="submit" id="send-message-btn">ارسال</button>
</div>
<div id="file-preview"></div>
</form>
</div>
</div>
<!-- نافذة المعاينة المنبثقة -->
<div id="previewModal" class="modal">
<div class="modal-content">
<button id="closeModal">&times;</button>
<div id="modalPreview"></div>
<button id="sendFromModal" class="send-button">إرسال</button>
</div>
</div>
<script>
$(document).ready(async function () {
const messageList = $('#messages-list');
const receiverId = {{ user.id }};
let selectedFile = null;
// إرسال طلب لتحديث حالة المستخدم إلى "داخل المحادثة"
await fetch('/join_chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
// تحديث جميع الرسائل إلى "مقروءة" عند فتح المحادثة
await fetch('/mark_all_as_seen', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ receiver_id: receiverId }),
});
// معالجة اختيار الملف
$('#file-input').change(function(e) {
const file = e.target.files[0];
if (file) {
selectedFile = file;
showFilePreview(file);
}
});
function showFilePreview(file) {
const preview = $('#file-preview');
preview.empty();
if (file.type.startsWith('image/')) {
preview.append(`<img src="${URL.createObjectURL(file)}" class="preview-image">`);
} else if (file.type.startsWith('video/')) {
preview.append(`
<video controls class="preview-video">
<source src="${URL.createObjectURL(file)}" type="${file.type}">
</video>
`);
}
preview.append(`
<div class="file-info">
<span>${file.name}</span>
<button type="button" class="remove-file-btn">×</button>
</div>
`);
$('.remove-file-btn').click(() => {
selectedFile = null;
preview.empty();
$('#file-input').val('');
});
}
async function loadMessages() {
try {
const response = await fetch(`/get_messages/${receiverId}`);
const messages = await response.json();
if (!Array.isArray(messages)) {
console.error('الرد ليس مصفوفة:', messages);
return;
}
messageList.empty();
messages.forEach(message => {
const messageItem = $('<div>')
.addClass('message-item')
.attr('data-message-id', message.id);
// تحديد إذا كانت الرسالة مرسلة أو مستقبلة
if (message.sender_id === {{ current_user.id }}) {
messageItem.addClass('sent');
// إضافة حالة المشاهدة للمرسل
if (message.watched_by_receiver) {
messageItem.append('<span class="seen-status">✓✓</span>'); // المستقبل شاهد الرسالة
} else {
messageItem.append('<span class="seen-status">✓</span>'); // الرسالة مرسلة، ولكن المستقبل لم يشاهدها بعد
}
} else {
messageItem.addClass('received');
// لا تظهر أي علامة للمستقبل
}
const contentContainer = $('<div>').addClass('message-content');
// عرض الوسائط (صور أو فيديوهات) إذا كانت موجودة
if (message.file_url) {
if (message.file_type.startsWith('image/')) {
// إذا كانت صورة
contentContainer.append(`<img src="/${message.file_url}" class="message-media">`);
} else if (message.file_type.startsWith('video/')) {
// إذا كان فيديو
contentContainer.append(`
<video controls class="message-media">
<source src="/${message.file_url}" type="${message.file_type}">
</video>
`);
}
}
// إضافة النصوص إذا كانت موجودة
if (message.content) {
contentContainer.append(`<span>${message.content}</span>`);
}
// إضافة الطابع الزمني
const timestamp = $('<span>')
.addClass('message-timestamp')
.text(message.created_at);
// إضافة جميع العناصر إلى الرسالة
messageItem.append(contentContainer).append(timestamp);
messageList.append(messageItem);
});
// تمرير التمرير لأسفل لرؤية آخر الرسائل
messageList.scrollTop(messageList.prop('scrollHeight'));
} catch (error) {
console.error('حدث خطأ أثناء تحميل الرسائل:', error);
}
}
// SSE لتحديث حالة المشاهدة
const updateEventSource = new EventSource(`/stream_message_updates/${receiverId}`);
updateEventSource.onmessage = function (event) {
const data = JSON.parse(event.data);
const messageId = data.message_id;
const isSender = data.sender_id === {{ current_user.id }};
// إذا كان المستخدم الحالي هو المرسل
if (isSender) {
const seenStatus = $(`.message-item[data-message-id="${messageId}"] .seen-status`);
if (data.watched_by_receiver) {
seenStatus.text('✓✓'); // المستقبل شاهد الرسالة
} else {
seenStatus.text('✓'); // الرسالة مرسلة، ولكن المستقبل لم يشاهدها بعد
}
}
};
updateEventSource.onerror = function () {
console.log('SSE error. Reconnecting...');
setTimeout(() => {
new EventSource(`/stream_message_updates/${receiverId}`);
}, 3000);
};
// SSE للرسائل الجديدة
const eventSource = new EventSource(`/stream_messages/${receiverId}`);
eventSource.onmessage = function (event) {
loadMessages();
};
// إرسال الرسالة
// عند اختيار ملف
// عند اختيار ملف
$('#file-input').change(function(e) {
const file = e.target.files[0];
if (file) {
selectedFile = file;
showFilePreview(file); // عرض معاينة الملف
}
});
// عرض معاينة الملف في نافذة منبثقة
function showFilePreview(file) {
const previewModal = $('#previewModal');
const modalPreview = $('#modalPreview');
// تنظيف المحتوى السابق
modalPreview.empty();
if (file.type.startsWith('image')) {
// عرض الصورة
const img = $('<img>').attr('src', URL.createObjectURL(file)).css({ maxWidth: '100%', maxHeight: '80vh' });
modalPreview.append(img);
} else if (file.type.startsWith('video')) {
// عرض الفيديو
const video = $('<video>').attr('src', URL.createObjectURL(file)).attr('controls', true).css({ maxWidth: '100%', maxHeight: '80vh' });
modalPreview.append(video);
}
// إظهار نافذة المعاينة
previewModal.show();
}
// إغلاق نافذة المعاينة
$('#closeModal').click(function() {
$('#previewModal').hide();
});
// إرسال الملف من نافذة المعاينة
$('#sendFromModal').click(function() {
$('#previewModal').hide();
$('#messageForm').submit();
});
// إرسال النموذج
$('#messageForm').submit(async function(e) {
e.preventDefault();
const messageContent = $('#message-input').val().trim();
const formData = new FormData();
formData.append('content', messageContent);
formData.append('receiver_id', receiverId);
if (selectedFile) {
formData.append('file', selectedFile);
// إظهار دائرة التحميل وشريط التقدم في مكان الرسائل
$('#messages-list').append(`
<div class="loading-container">
<div class="loading-spinner">جاري التحميل...</div>
<div class="progress-bar">
<div class="progress"></div>
</div>
</div>
`);
}
if (!messageContent && !selectedFile) return;
try {
const isReceiverActive = await fetch(`/is_user_active/${receiverId}`).then(res => res.json());
// استخدام XMLHttpRequest لتتبع تقدم الرفع
const xhr = new XMLHttpRequest();
xhr.open('POST', '/send_message', true);
// تحديث شريط التقدم أثناء الرفع
xhr.upload.onprogress = function(event) {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
$('.progress').css('width', percentComplete + '%');
}
};
xhr.onload = function() {
if (xhr.status === 200) {
$('#message-input').val('');
$('#file-preview').empty();
selectedFile = null;
$('#file-input').val('');
if (isReceiverActive.active) {
const messageId = JSON.parse(xhr.responseText).message_id;
fetch(`/mark_message_as_seen/${messageId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
}
// إخفاء دائرة التحميل وشريط التقدم بعد اكتمال الإرسال
$('.loading-container').remove();
loadMessages();
} else {
console.error('حدث خطأ أثناء إرسال الرسالة:', xhr.statusText);
$('.loading-container').remove();
}
};
xhr.onerror = function() {
console.error('حدث خطأ أثناء إرسال الرسالة:', xhr.statusText);
$('.loading-container').remove();
};
xhr.send(formData);
} catch (error) {
console.error('حدث خطأ أثناء إرسال الرسالة:', error);
// إخفاء دائرة التحميل وشريط التقدم في حالة حدوث خطأ
$('.loading-container').remove();
}
});
await loadMessages();
});
window.addEventListener('beforeunload', async function () {
await fetch('/leave_chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
if (updateEventSource) updateEventSource.close();
if (eventSource) eventSource.close();
});
</script>
{% endblock %}