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
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, "<").replace(/>/g, ">");
|
|
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);
|
|
|
|
});
|