| | {% 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 {
|
| |
|
| | 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;
|
| | 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">×</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);
|
| | }
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| | 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);
|
| | };
|
| |
|
| |
|
| | 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());
|
| |
|
| |
|
| | 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 %} |