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.
 
 
 
 

280 lines
12 KiB

document.addEventListener('DOMContentLoaded', function () {
let currentRoomId = null;
let lastMessageId = 0;
let messagePollInterval = null;
let roomPollInterval = null;
let isFetchingMessages = false;
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'),
// 🟢 متغیرهای جدید برای Modal کاربران
newChatBtn: document.getElementById('new-chat-btn'),
usersModal: document.getElementById('users-modal'),
closeModalBtn: document.getElementById('close-users-modal'),
usersList: document.getElementById('users-list'),
searchInput: document.getElementById('user-search-input')
};
// ── منطق مودال کاربران ──
if (UI.newChatBtn) {
UI.newChatBtn.addEventListener('click', () => {
UI.usersModal.style.transform = 'translateY(0)'; // نمایش مودال (بالا آمدن)
fetchUsers(''); // لود اولیه لیست کاربران
});
}
if (UI.closeModalBtn) {
UI.closeModalBtn.addEventListener('click', () => {
UI.usersModal.style.transform = 'translateY(100%)'; // مخفی کردن مودال (پایین رفتن)
});
}
if (UI.searchInput) {
let timeout = null;
UI.searchInput.addEventListener('input', (e) => {
clearTimeout(timeout);
// دی‌باونس (Debounce) برای جلوگیری از ریکوئست‌های تکراری
timeout = setTimeout(() => {
fetchUsers(e.target.value);
}, 300);
});
}
async function fetchUsers(query) {
UI.usersList.innerHTML = '<div class="flex items-center justify-center h-20"><span class="material-symbols-outlined animate-spin text-gray-400">autorenew</span></div>';
try {
const res = await fetch(`${window.CHAT_CONFIG.apiUrlUsers}?q=${encodeURIComponent(query)}`);
const data = await res.json();
renderUsers(data.users);
} catch (e) {
console.error("Failed to fetch users:", e);
UI.usersList.innerHTML = '<p class="text-center text-red-400 mt-4 text-sm">Failed to load users.</p>';
}
}
function renderUsers(users) {
if (users.length === 0) {
UI.usersList.innerHTML = '<p class="text-center text-gray-500 mt-6 text-sm font-medium">No users found.</p>';
return;
}
UI.usersList.innerHTML = users.map(user => {
const initial = user.name ? user.name.charAt(0).toUpperCase() : 'U';
// استفاده از کلاس‌های room-item و room-avatar برای هماهنگی 100 درصدی
return `
<div onclick="startChatWithUser(${user.id})" class="room-item flex items-center gap-3 p-3 cursor-pointer">
<div class="room-avatar" style="background: linear-gradient(135deg, #6366f1, #4f46e5); box-shadow: 0 2px 8px rgba(99,102,241,0.3);">
${initial}
</div>
<div class="flex-1 min-w-0">
<div class="mb-1.5">
<span class="font-semibold text-[14px] text-gray-900 dark:text-gray-100 truncate block">${user.name}</span>
</div>
<div class="flex items-center">
<span class="text-[10px] font-medium tracking-wide text-gray-500 uppercase bg-gray-200 dark:bg-gray-700 px-2 py-0.5 rounded-full">${user.role}</span>
</div>
</div>
</div>
`;
}).join('');
}
window.startChatWithUser = async function (userId) {
// بستن مودال بلافاصله بعد از کلیک
UI.usersModal.style.transform = 'translateY(100%)';
try {
const res = await fetch(window.CHAT_CONFIG.apiUrlCreateRoom, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': window.CHAT_CONFIG.csrfToken
},
body: JSON.stringify({ user_id: userId })
});
const data = await res.json();
if (data.success) {
// اگر با موفقیت ساخته یا پیدا شد، مستقیماً وارد چت می‌شویم
selectRoom(data.room_id, data.title);
}
} catch (e) {
console.error("Failed to start chat:", e);
}
};
// ── پایان منطق مودال کاربران ──
// ── بقیه کدهای قبلی (fetchRooms, renderRooms, fetchMessages و...) بدون تغییر در اینجا قرار دارند ──
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, "\\'");
const initial = room.title ? room.title.charAt(0).toUpperCase() : 'G';
return `
<div onclick="selectRoom(${room.id}, '${safeTitle}')"
class="room-item flex items-center gap-3 p-3 cursor-pointer ${isActive ? 'room-item-active' : ''}">
<div class="room-avatar">
${initial}
</div>
<div class="flex-1 min-w-0">
<div class="flex justify-between items-center mb-1">
<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-[12px] font-medium text-gray-500 truncate">${room.type === 'group' ? '👥 Group Chat' : '👤 Private Chat'}</span>
${room.unread > 0 ? `<span class="bg-blue-500 shadow-sm text-white text-[10px] font-bold px-2 py-0.5 rounded-full">${room.unread}</span>` : ''}
</div>
</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 gap-2"><span class="material-symbols-outlined text-5xl opacity-30">chat</span><p class="text-sm font-medium">No messages yet. Start the conversation!</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); }
});
function startPolling() {
messagePollInterval = setInterval(fetchMessages, 3000);
}
function stopPolling() {
if (messagePollInterval) clearInterval(messagePollInterval);
}
fetchRooms();
roomPollInterval = setInterval(fetchRooms, 10000);
});