You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

197 lines
8.0 KiB

document.addEventListener('DOMContentLoaded', function () {
// متغیرهای وضعیت
let currentRoomId = null;
let lastMessageId = 0;
let messagePollInterval = null;
let roomPollInterval = null;
// 🟢 متغیر قفل برای جلوگیری از دریافت پیام‌های تکراری (Race Condition)
let isFetchingMessages = false;
// المان‌های DOM
const UI = {
roomList: document.getElementById('room-list'),
noChatSelected: document.getElementById('no-chat-selected'),
chatHeader: document.getElementById('active-chat-header'),
chatTitle: document.getElementById('active-chat-title'),
messages: document.getElementById('chat-messages'),
inputArea: document.getElementById('chat-input-area'),
form: document.getElementById('chat-form'),
input: document.getElementById('chat-input'),
indicator: document.getElementById('polling-indicator')
};
// گرفتن لیست چت‌ها از سرور
async function fetchRooms() {
try {
const res = await fetch(window.CHAT_CONFIG.apiUrlRooms);
const data = await res.json();
renderRooms(data.rooms);
} catch (e) {
console.error("Failed to fetch rooms:", e);
}
}
// رندر کردن لیست چت‌ها
function renderRooms(rooms) {
if (rooms.length === 0) {
UI.roomList.innerHTML = '<p class="text-center text-sm text-gray-500 mt-10 px-4">No active chats found.</p>';
return;
}
UI.roomList.innerHTML = rooms.map(room => {
const isActive = currentRoomId === room.id;
const safeTitle = room.title.replace(/'/g, "\\'");
return `
<div onclick="selectRoom(${room.id}, '${safeTitle}')"
class="group px-5 py-4 cursor-pointer border-b border-gray-200 dark:border-gray-800 transition-all duration-150 border-l-4 ${isActive ? 'room-item-active' : 'border-l-transparent hover:bg-gray-100 dark:hover:bg-gray-800/60'}">
<div class="flex justify-between items-center mb-1.5">
<span class="font-semibold text-[14px] text-gray-900 dark:text-gray-100 truncate pr-2">${room.title}</span>
<span class="text-[11px] text-gray-400 whitespace-nowrap flex-shrink-0">${room.time}</span>
</div>
<div class="flex justify-between items-center">
<span class="text-[10px] font-medium text-gray-500 uppercase bg-gray-200 dark:bg-gray-700 px-2 py-0.5 rounded">${room.type}</span>
${room.unread > 0 ? `<span class="bg-red-500 shadow-sm text-white text-[10px] font-bold px-2 py-0.5 rounded-full">${room.unread}</span>` : ''}
</div>
</div>
`;
}).join('');
}
// انتخاب یک چت
window.selectRoom = function (id, title) {
currentRoomId = id;
lastMessageId = 0;
isFetchingMessages = false; // ریست کردن قفل
UI.noChatSelected.classList.add('hidden');
UI.chatHeader.classList.remove('invisible');
UI.messages.classList.remove('invisible');
UI.inputArea.classList.remove('invisible');
UI.chatTitle.textContent = title;
UI.messages.innerHTML = '<div class="flex items-center justify-center h-full"><span class="material-symbols-outlined animate-spin text-gray-400 text-3xl">autorenew</span></div>';
fetchRooms();
stopPolling();
fetchMessages().then(() => {
startPolling();
scrollToBottom();
});
UI.input.focus();
};
// گرفتن پیام‌ها از سرور
async function fetchMessages() {
// 🟢 اگر هیچ رومی انتخاب نشده، یا درخواست قبلی هنوز تمام نشده، برگرد!
if (!currentRoomId || isFetchingMessages) return;
isFetchingMessages = true; // 🟢 فعال کردن قفل
try {
UI.indicator.classList.remove('hidden');
setTimeout(() => UI.indicator.classList.add('hidden'), 500);
const res = await fetch(`${window.CHAT_CONFIG.apiUrlMessagesBase}${currentRoomId}/?last_id=${lastMessageId}`);
const data = await res.json();
if (data.messages && data.messages.length > 0) {
if (lastMessageId === 0) UI.messages.innerHTML = '';
renderMessages(data.messages);
lastMessageId = data.messages[data.messages.length - 1].id;
scrollToBottom();
} else if (lastMessageId === 0) {
UI.messages.innerHTML = `
<div class="flex flex-col items-center justify-center h-full text-gray-400 opacity-60">
<span class="material-symbols-outlined text-5xl mb-2">forum</span>
<p class="text-sm font-medium">No messages yet. Say hi!</p>
</div>`;
}
} catch (e) {
console.error("Failed to fetch messages:", e);
} finally {
isFetchingMessages = false; // 🟢 باز کردن قفل در هر شرایطی (چه موفق چه ارور)
}
}
// رندر کردن حباب‌های پیام
function renderMessages(messages) {
const html = messages.map(m => {
const isMe = m.is_me;
const alignClass = isMe ? 'message-sent' : 'message-received';
const bubbleClass = isMe ? 'message-bubble-sent' : 'message-bubble-received';
const senderName = !isMe ? `<div class="text-[11px] font-bold text-blue-600 dark:text-blue-400 mb-1 opacity-90">${m.sender_name}</div>` : '';
const safeContent = m.content.replace(/</g, "&lt;").replace(/>/g, "&gt;");
const timeColor = isMe ? 'text-blue-100' : 'text-gray-400';
const tickIcon = isMe ? `<span class="material-symbols-outlined text-[13px] ml-1 text-white opacity-80">done_all</span>` : '';
return `
<div class="message-bubble ${alignClass} w-full">
<div class="${bubbleClass}">
<div class="message-content">
${senderName}
<p class="m-0">${safeContent}</p>
<div class="message-meta ${timeColor} flex items-center justify-end">
${m.sent_at} ${tickIcon}
</div>
</div>
</div>
</div>
`;
}).join('');
UI.messages.insertAdjacentHTML('beforeend', html);
}
// اسکرول به پایین
function scrollToBottom() {
UI.messages.scrollTo({
top: UI.messages.scrollHeight,
behavior: 'smooth'
});
}
// ارسال پیام جدید
UI.form.addEventListener('submit', async (e) => {
e.preventDefault();
const text = UI.input.value.trim();
if (!text || !currentRoomId) return;
UI.input.value = '';
try {
await fetch(`${window.CHAT_CONFIG.apiUrlSendBase}${currentRoomId}/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': window.CHAT_CONFIG.csrfToken
},
body: JSON.stringify({ content: text })
});
// 🟢 صدا زدن دریافت پیام به صورت ایمن
await fetchMessages();
fetchRooms();
} catch (e) {
console.error("Failed to send message:", e);
}
});
// راه‌اندازی Polling
function startPolling() {
messagePollInterval = setInterval(fetchMessages, 3000);
}
function stopPolling() {
if (messagePollInterval) clearInterval(messagePollInterval);
}
// مقداردهی اولیه
fetchRooms();
roomPollInterval = setInterval(fetchRooms, 10000);
});