// ── Messages page ───────────────────────────────────────────────────────────── const composeModal = new bootstrap.Modal('#composeModal'); let _currentTab = 'inbox'; // 'inbox' | 'sent' let _messages = []; // current list let _openId = null; // currently open message id let _recipientId = null; // chosen recipient in compose // ── Tab switching ───────────────────────────────────────────────────────────── document.querySelectorAll('#msgTabs .nav-link').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('#msgTabs .nav-link').forEach(b => b.classList.remove('active')); btn.classList.add('active'); _currentTab = btn.dataset.tab; _openId = null; showDetail(null); loadMessages(); }); }); // ── Load messages ───────────────────────────────────────────────────────────── async function loadMessages() { document.getElementById('msgList').innerHTML = '
Loading…
'; try { const url = _currentTab === 'sent' ? '/social/messages/sent/' : '/social/messages/'; _messages = await apiGet(url); renderList(); loadUnreadCount(); } catch (e) { document.getElementById('msgList').innerHTML = '
Failed to load messages.
'; } } function renderList() { const el = document.getElementById('msgList'); if (!_messages.length) { el.innerHTML = '
No messages here yet.
'; return; } el.innerHTML = _messages.map(m => { const other = _currentTab === 'sent' ? m.recipient_detail : m.sender_detail; const isUnread = _currentTab === 'inbox' && !m.read_at; const date = m.sent_at ? new Date(m.sent_at).toLocaleDateString() : ''; return `
${isUnread ? '' : ''}
${esc(m.subject)} ${esc(date)}
${esc(other?.display_name || other?.username || '—')}
`; }).join(''); } // ── Open a message ──────────────────────────────────────────────────────────── async function openMessage(id) { _openId = id; // Highlight document.querySelectorAll('.msg-row').forEach(r => { r.classList.toggle('active', Number(r.dataset.id) === id); }); try { const msg = await apiGet(`/social/messages/${id}/`); showDetail(msg); // Mark as read visually const row = document.querySelector(`.msg-row[data-id="${id}"]`); if (row) row.classList.remove('unread'); // Refresh unread badge loadUnreadCount(); } catch (e) { showToast('Failed to load message.', 'danger'); } } function showDetail(msg) { const empty = document.getElementById('detailEmpty'); const content = document.getElementById('detailContent'); if (!msg) { empty.classList.remove('d-none'); content.classList.add('d-none'); return; } empty.classList.add('d-none'); content.classList.remove('d-none'); document.getElementById('detailSubject').textContent = msg.subject; document.getElementById('detailBody').textContent = msg.body; const from = msg.sender_detail?.display_name || msg.sender_detail?.username || '—'; const to = msg.recipient_detail?.display_name || msg.recipient_detail?.username || '—'; const date = msg.sent_at ? new Date(msg.sent_at).toLocaleString() : ''; document.getElementById('detailMeta').innerHTML = ` From: ${esc(from)} To: ${esc(to)} ${esc(date)}`; // Reply button prefills compose document.getElementById('detailReplyBtn').onclick = () => { if (_currentTab === 'inbox' && msg.sender_detail) { prefillCompose(msg.sender_detail, `Re: ${msg.subject}`); } else { prefillCompose(msg.recipient_detail, `Re: ${msg.subject}`); } }; } // ── Delete ──────────────────────────────────────────────────────────────────── document.getElementById('detailDeleteBtn').addEventListener('click', async () => { if (!_openId || !confirm('Delete this message?')) return; try { await apiDelete(`/social/messages/${_openId}/`); _messages = _messages.filter(m => m.id !== _openId); _openId = null; renderList(); showDetail(null); showToast('Message deleted.'); } catch (e) { showToast('Delete failed.', 'danger'); } }); // ── Compose ─────────────────────────────────────────────────────────────────── document.getElementById('composeBtn').addEventListener('click', () => openCompose()); function openCompose(recipientDetail = null, subject = '') { clearCompose(); if (recipientDetail) prefillCompose(recipientDetail, subject); composeModal.show(); } function prefillCompose(recipientDetail, subject) { clearCompose(); _recipientId = recipientDetail.id; document.getElementById('recipientId').value = recipientDetail.id; document.getElementById('recipientLabel').textContent = recipientDetail.display_name || recipientDetail.username; document.getElementById('recipientChosen').classList.remove('d-none'); document.getElementById('recipientSearch').disabled = true; document.getElementById('composeSubject').value = subject; composeModal.show(); } function clearCompose() { _recipientId = null; document.getElementById('recipientId').value = ''; document.getElementById('recipientSearch').value = ''; document.getElementById('recipientSearch').disabled = false; document.getElementById('recipientResults').classList.add('d-none'); document.getElementById('recipientResults').innerHTML = ''; document.getElementById('recipientChosen').classList.add('d-none'); document.getElementById('recipientLabel').textContent = ''; document.getElementById('composeSubject').value = ''; document.getElementById('composeBody').value = ''; document.getElementById('composeAlert').classList.add('d-none'); } document.getElementById('recipientClear').addEventListener('click', () => { _recipientId = null; document.getElementById('recipientId').value = ''; document.getElementById('recipientChosen').classList.add('d-none'); document.getElementById('recipientSearch').disabled = false; document.getElementById('recipientSearch').value = ''; document.getElementById('recipientSearch').focus(); }); // Recipient search autocomplete let _searchTimer = null; document.getElementById('recipientSearch').addEventListener('input', function () { clearTimeout(_searchTimer); const q = this.value.trim(); if (q.length < 2) { document.getElementById('recipientResults').classList.add('d-none'); return; } _searchTimer = setTimeout(async () => { try { const results = await apiGet(`/social/members/?q=${encodeURIComponent(q)}`); renderRecipientResults(results); } catch (e) { /* ignore */ } }, 300); }); function renderRecipientResults(results) { const el = document.getElementById('recipientResults'); if (!results.length) { el.classList.add('d-none'); return; } el.innerHTML = results.map(u => `` ).join(''); el.classList.remove('d-none'); el.querySelectorAll('button').forEach(btn => { btn.addEventListener('click', () => { _recipientId = Number(btn.dataset.uid); document.getElementById('recipientId').value = _recipientId; document.getElementById('recipientLabel').textContent = btn.dataset.name; document.getElementById('recipientChosen').classList.remove('d-none'); document.getElementById('recipientSearch').disabled = true; document.getElementById('recipientSearch').value = ''; el.classList.add('d-none'); }); }); } document.getElementById('composeSendBtn').addEventListener('click', async () => { const alertEl = document.getElementById('composeAlert'); alertEl.classList.add('d-none'); if (!_recipientId) { alertEl.textContent = 'Please select a recipient.'; alertEl.classList.remove('d-none'); return; } const subject = document.getElementById('composeSubject').value.trim(); const body = document.getElementById('composeBody').value.trim(); if (!subject) { alertEl.textContent = 'Subject is required.'; alertEl.classList.remove('d-none'); return; } if (!body) { alertEl.textContent = 'Message body is required.'; alertEl.classList.remove('d-none'); return; } const btn = document.getElementById('composeSendBtn'); const spin = document.getElementById('composeSpin'); btn.disabled = true; spin.classList.remove('d-none'); try { await apiPost('/social/messages/', { recipient: _recipientId, subject, body }); composeModal.hide(); showToast('Message sent.'); if (_currentTab === 'sent') loadMessages(); } catch (e) { alertEl.textContent = 'Failed to send message.'; alertEl.classList.remove('d-none'); } finally { btn.disabled = false; spin.classList.add('d-none'); } }); // ── Unread count ────────────────────────────────────────────────────────────── async function loadUnreadCount() { try { const data = await apiGet('/social/messages/unread-count/'); const badge = document.getElementById('unreadBadge'); if (data.unread > 0) { badge.textContent = data.unread; badge.classList.remove('d-none'); } else { badge.classList.add('d-none'); } } catch (e) { /* ignore */ } } // ── Boot ────────────────────────────────────────────────────────────────────── loadMessages();