// /messages/messages.js
(function () {
const cfg = window.MESSAGING_CFG || {};
const app = document.querySelector('.msg-app');
const convList = document.getElementById('conv-list');
const threadPanel = document.getElementById('msg-thread-panel');
const titleEl = document.getElementById('thread-title');
const msgsEl = document.getElementById('messages');
const btnBack = document.getElementById('btn-back');
const form = document.getElementById('send-form');
const input = document.getElementById('msg-input');
const fileInput = document.getElementById('msg-file');
const hint = document.getElementById('send-hint');
const btnArchive = document.getElementById('btn-archive');
const btnClose = document.getElementById('btn-close');
// État
let currentConv = Number(threadPanel?.dataset?.convId || cfg.initialConv || 0) || 0;
let lastMsgId = 0;
let pollTimer = null;
let isArchived = false;
let isClosed = false;
// Utils
function qs(obj) { return new URLSearchParams(obj).toString(); }
function h(s) { const d = document.createElement('div'); d.textContent = (s ?? ''); return d.innerHTML; }
function atBottom(el) { return Math.abs(el.scrollHeight - el.scrollTop - el.clientHeight) < 6; }
function scrollToBottom() { msgsEl.scrollTop = msgsEl.scrollHeight; }
function setShowThread(on) { if (on) app.classList.add('show-thread'); else app.classList.remove('show-thread'); }
// ===== API helpers =====
async function apiListConversations() {
const r = await fetch('/messages/api/list_conversations.php', { credentials: 'same-origin' });
const j = await r.json();
if (!r.ok) throw new Error('list_failed');
return j.items || [];
}
async function apiFetchMessages(onlyNew) {
if (!currentConv) return [];
const url = new URL('/messages/api/fetch_messages.php', location.origin);
url.searchParams.set('conversation_id', String(currentConv));
if (onlyNew && lastMsgId) url.searchParams.set('since_id', String(lastMsgId));
const r = await fetch(url.toString(), { credentials: 'same-origin' });
const j = await r.json();
if (!r.ok) throw new Error('fetch_failed');
return j.items || [];
}
async function apiSendMessage(fd) {
const r = await fetch('/messages/api/send_message.php', { method: 'POST', body: fd, credentials: 'same-origin' });
const j = await r.json().catch(() => ({}));
if (!r.ok || !j.ok) throw new Error(j.error || 'send_failed');
return j;
}
async function apiMarkRead() {
if (!currentConv) return;
try {
const res = await fetch('/messages/api/mark_read.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
credentials: 'same-origin',
body: qs({ conversation_id: String(currentConv) })
});
const data = await res.json();
if (data.ok && window.applyBadges) {
window.applyBadges(data);
}
} catch (e) {
// silencieux volontairement
}
}
async function apiDeleteMessage(id) {
const fd = new FormData();
fd.set('_csrf', cfg.csrf);
fd.set('message_id', String(id));
const r = await fetch('/messages/api/delete_message.php', { method: 'POST', body: fd, credentials: 'same-origin' });
const j = await r.json();
if (!r.ok || !j.ok) throw new Error(j.error || 'delete_failed');
return j;
}
async function apiArchiveConversation(convId, archive) {
const fd = new FormData();
fd.set('_csrf', cfg.csrf);
fd.set('conversation_id', String(convId));
fd.set('archive', String(archive ? 1 : 0));
const r = await fetch('/messages/api/archive_conversation.php', { method: 'POST', body: fd, credentials: 'same-origin' });
const j = await r.json();
if (!r.ok || !j.ok) throw new Error(j.error || 'archive_failed');
return j;
}
async function apiCloseConversation(convId, close) {
const fd = new FormData();
fd.set('_csrf', cfg.csrf);
fd.set('conversation_id', String(convId));
fd.set('close', String(close ? 1 : 0));
const r = await fetch('/messages/api/close_conversation.php', { method: 'POST', body: fd, credentials: 'same-origin' });
const j = await r.json();
if (!r.ok || !j.ok) throw new Error(j.error || 'close_failed');
return j;
}
async function apiGetConversation(convId) {
const url = new URL('/messages/api/get_conversation.php', location.origin);
url.searchParams.set('conversation_id', String(convId));
const r = await fetch(url.toString(), { credentials: 'same-origin' });
const j = await r.json();
if (!r.ok || !j.ok) throw new Error(j.error || 'meta_failed');
return j; // {status, archived_for_me, ...}
}
// ===== Rendu UI =====
async function loadConversations() {
try {
const items = await apiListConversations();
renderConversations(items);
} catch (e) {
convList.innerHTML = `
API indisponible
`;
}
}
function renderConversations(items){
convList.innerHTML = '';
if (!items.length){
const el = document.createElement('div');
el.className = 'conv-item';
el.innerHTML = `
${h(cfg.i18n?.noConversations || 'Sélectionner une conversation')}
`;
convList.appendChild(el);
return;
}
items.forEach(it => {
const btn = document.createElement('div'); // div pour pouvoir avoir 2 zones cliquables
btn.className = 'conv-item';
btn.innerHTML = `
${h(it.peer_name || ('#'+it.id))}
${h(it.last_body || '')}
${Number(it.unread||0) ? `${it.unread}` : ''}
`;
// ouvrir la conversation si on clique sur le bloc texte
btn.querySelector('.conv-main').onclick = () => openConversation(Number(it.id));
// suppression via la corbeille
btn.querySelector('.conv-delete').onclick = async (e) => {
e.stopPropagation();
if (!confirm(cfg.i18n?.deleteConvConfirm || 'Supprimer cette conversation ?')) return;
try{
await apiDeleteConversation(it.id);
// Si on est en train de regarder cette conv, on ferme le thread
if (Number(threadPanel.dataset.convId||0) === Number(it.id)) {
threadPanel.dataset.convId = '0';
titleEl.textContent = cfg.i18n?.noConversations || 'Sélectionner une conversation';
msgsEl.innerHTML = '';
setShowThread(false);
const url = new URL(window.location.href);
url.searchParams.delete('conv');
window.history.replaceState(null, '', url.toString());
}
await loadConversations();
}catch(err){
alert(cfg.i18n?.deleteConvFail || 'Suppression impossible.');
}
};
convList.appendChild(btn);
});
}
async function apiDeleteConversation(convId){
const fd = new FormData();
fd.set('_csrf', cfg.csrf);
fd.set('conversation_id', String(convId));
const r = await fetch('/messages/api/delete_conversation.php', { method:'POST', body: fd, credentials: 'same-origin' });
const j = await r.json();
if (!r.ok || !j.ok) throw new Error(j.error || 'delete_conv_failed');
return j;
}
async function fetchAndRender(onlyNew) {
if (!currentConv) return;
try {
const items = await apiFetchMessages(onlyNew);
renderMessages(items, !!onlyNew);
} catch (e) {
// silencieux
}
}
function renderMessages(items, append) {
if (!append) msgsEl.innerHTML = '';
const stick = atBottom(msgsEl);
items.forEach(m => {
const mine = (m.sender_role === cfg.role && Number(m.sender_id) === Number(cfg.meId));
const wrap = document.createElement('div');
wrap.className = 'msg' + (mine ? ' me' : '');
wrap.dataset.id = String(m.id);
const parts = [];
if (m.body) parts.push(`
${h(m.body)}
`);
if (m.attachment_path) {
const name = m.attachment_name || 'fichier';
parts.push(`