let contacts = []; let activeOccupantId = null; let activeAvatarUrl = ''; function formatTime(dateStr) { if(!dateStr) return ''; const date = new Date(dateStr); let hours = date.getHours(); let minutes = date.getMinutes(); const ampm = hours >= 12 ? 'PM' : 'AM'; hours = hours % 12; hours = hours ? hours : 12; minutes = minutes < 10 ? '0'+minutes : minutes; return hours + ':' + minutes + ' ' + ampm; } function formatRelative(dateStr) { if(!dateStr) return ''; const date = new Date(dateStr); const now = new Date(); const diffMs = now - date; const diffMins = Math.floor(diffMs / 60000); if(diffMins < 60) return diffMins + ' Min'; const diffHours = Math.floor(diffMins / 60); if(diffHours < 24) return diffHours + ' Hour'; const diffDays = Math.floor(diffHours / 24); return diffDays + ' Day'; } function loadContacts() { $.post('chat_api.php', { action: 'get_contacts' }, function(res) { if(res.status === 'success') { contacts = res.data; renderContacts(contacts); } }, 'json'); } function renderContacts(data) { let chatsHtml = ''; let contactsHtml = ''; const nowServer = new Date(); data.forEach(c => { let activeClass = (c.id_occupant == activeOccupantId) ? 'active' : ''; // Cek jika pesan berisi media sebelumnya dicadangkan sbg teks dummy let rawLastMsg = c.last_message || ''; let strippedMsg = rawLastMsg.replace(/<[^>]*>?/gm, '').trim(); if(!strippedMsg && rawLastMsg.includes(' 0 && c.id_occupant != activeOccupantId) { unreadBadge = `${c.unread_count}`; nameStyle = 'font-weight: 800; color: #000;'; msgStyle = 'font-weight: 700; color: #222;'; } let html = `
${c.name}
${c.name}${unreadBadge}
${timeStr}
${lastMsg}
`; if(c.last_time) { chatsHtml += html; } else { contactsHtml += html; } }); if(chatsHtml) { document.getElementById('chatsList').innerHTML = chatsHtml; document.getElementById('chatsSection').style.display = 'block'; } else { document.getElementById('chatsSection').style.display = 'none'; } if(contactsHtml) { document.getElementById('contactsList').innerHTML = contactsHtml; document.getElementById('contactsSection').style.display = 'block'; } else { document.getElementById('contactsSection').style.display = 'none'; } } function filterContacts() { let q = document.getElementById('searchInput').value.toLowerCase(); let filtered = contacts.filter(c => c.name.toLowerCase().includes(q)); renderContacts(filtered); } function openChat(id) { activeOccupantId = id; renderContacts(contacts); document.getElementById('chatBlank').style.display = 'none'; document.getElementById('chatActive').style.display = 'flex'; document.getElementById('chatHistory').innerHTML = '
Loading...
'; $.post('chat_api.php', { action: 'get_messages', id_occupant: id }, function(res) { if(res.status === 'success') { document.getElementById('activeName').innerText = res.occupant.name; let occStatStr = 'Status tidak diketahui'; if (res.occupant.last_active) { let lastActStr = res.occupant.last_active.replace(/-/g, '/'); let actDate = new Date(lastActStr); let diffSec = (new Date() - actDate) / 1000; let diffDays = diffSec / (60 * 60 * 24); if (diffSec < 20) { occStatStr = 'Online'; } else if (diffDays > 7) { let jam = actDate.getHours().toString().padStart(2, '0'); let menit = actDate.getMinutes().toString().padStart(2, '0'); let tgl = actDate.getDate().toString().padStart(2, '0'); let bln = (actDate.getMonth() + 1).toString().padStart(2, '0'); let thn = actDate.getFullYear(); occStatStr = `Terakhir Online ${jam}.${menit} ${tgl}-${bln}-${thn}`; } else { const hariArr = ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu']; let namaHari = hariArr[actDate.getDay()]; let jam = actDate.getHours().toString().padStart(2, '0'); let menit = actDate.getMinutes().toString().padStart(2, '0'); occStatStr = `Terakhir Online Jam ${jam}:${menit} Hari ${namaHari}`; } } document.getElementById('activeStatus').innerText = occStatStr; document.getElementById('activeAvatar').src = res.occupant.foto; activeAvatarUrl = res.occupant.foto; renderMessages(res.data); loadContacts(); } }, 'json'); } function buildMessagesHtml(messages) { let html = ''; const adminAvatar = 'images/admin.png'; messages.forEach(msg => { let isRight = (msg.sender === 'account'); let cls = isRight ? 'msg-right' : 'msg-left'; let statusHtml = ''; if (isRight) { if (msg.is_read == 1) { statusHtml = ''; } else if (msg.telegram_msg_id) { statusHtml = ''; } else { statusHtml = ''; } } let avatarToUse = isRight ? adminAvatar : activeAvatarUrl; let onContextMenu = `oncontextmenu="showContextMenu(event, ${msg.id_chat}, '${msg.sender}', this); return false;"`; let replyBlock = ''; if (msg.reply_to_chat_id && msg.reply_message) { let rpSender = (msg.reply_sender === 'account') ? 'Anda' : document.getElementById('activeName').innerText; let strippedRpMsg = msg.reply_message.replace(/<[^>]*>?/gm, ''); if(!strippedRpMsg) strippedRpMsg = 'Lampiran/File'; replyBlock = `
` + `
${rpSender}
` + `
${strippedRpMsg}
` + `
`; } let msgOutput = msg.message; let timeOutput = `
${formatTime(msg.created_at)} ${statusHtml}
`; let bubbleClass = 'msg-bubble'; // Additional Logic for Selection Mode let selectionClasses = ''; if (isSelectionMode) { selectionClasses = 'selectable'; if (selectedMessages.some(sm => sm.id === msg.id_chat)) selectionClasses += ' selected'; } let checkboxHtml = `
`; let msgRowClick = isSelectionMode ? `onclick="toggleSelectMessage(event, ${msg.id_chat}, '${msg.sender}')"` : ''; html += `
${checkboxHtml}
${replyBlock}${msgOutput}
${timeOutput}
`; }); return html; } function renderMessages(messages) { let hist = document.getElementById('chatHistory'); let newHtml = buildMessagesHtml(messages); if (hist.innerHTML !== newHtml) { let isScrolledBottom = hist.scrollHeight - hist.clientHeight <= hist.scrollTop + 10; hist.innerHTML = newHtml; if (isScrolledBottom || hist.scrollTop === 0) { scrollToBottom(); } } } function scrollToBottom() { let hist = document.getElementById('chatHistory'); hist.scrollTop = hist.scrollHeight; } function scrollToMsg(id) { let el = document.getElementById('msg-' + id); if(el) { el.scrollIntoView({behavior: "smooth", block: "center"}); el.style.backgroundColor = 'rgba(88, 86, 214, 0.2)'; setTimeout(() => { el.style.backgroundColor = 'transparent'; }, 1000); } } function handleEnter(e) { if(e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); // Mencegah pindah baris submitMessageOrEdit(); } } // Auto-resize textarea document.addEventListener('input', function (e) { if (e.target.id === 'messageInput') { e.target.style.height = 'auto'; // Reset height e.target.style.height = (e.target.scrollHeight) + 'px'; // Set to scroll height } }); function submitMessageOrEdit() { if (editingMsgId) { saveEdit(); } else { sendMessage(); } } function sendMessage() { let input = document.getElementById('messageInput'); let text = input.value.trim(); if(!text || !activeOccupantId) return; let formData = new FormData(); formData.append('action', 'send_message'); formData.append('id_occupant', activeOccupantId); formData.append('message', text); formData.append('sender', 'account'); if (replyingToId) formData.append('reply_to_chat_id', replyingToId); let tempId = 'temp-' + Date.now(); let optStatus = ''; let timeOutput = `
Mengirim... ${optStatus}
`; let hist = document.getElementById('chatHistory'); let tempRow = document.createElement('div'); tempRow.className = 'msg-row msg-right msg-sending'; tempRow.id = tempId; let escapedText = text.replace(//g, '>'); tempRow.innerHTML = `
${escapedText}
${timeOutput}
`; hist.appendChild(tempRow); scrollToBottom(); input.value = ''; input.style.height = 'auto'; // Reset textarea height closeReplyBox(); $.ajax({ url: 'chat_api.php', type: 'POST', data: formData, contentType: false, processData: false, dataType: 'json', success: function(res) { if(res.status === 'success') { loadContacts(); $.post('chat_api.php', { action: 'get_messages', id_occupant: activeOccupantId }, function(res2) { if(res2.status === 'success') { renderMessages(res2.data); } }, 'json'); } else { let el = document.getElementById(tempId); if (el) { el.querySelector('.msg-status').innerHTML = ''; el.classList.remove('msg-sending'); } } }, error: function() { let el = document.getElementById(tempId); if (el) { el.querySelector('.msg-status').innerHTML = ''; el.classList.remove('msg-sending'); } } }); } // โ•โ•โ• EMOJI PICKER โ•โ•โ• const emojiData = { smileys: ['๐Ÿ˜€','๐Ÿ˜ƒ','๐Ÿ˜„','๐Ÿ˜','๐Ÿ˜†','๐Ÿ˜…','๐Ÿคฃ','๐Ÿ˜‚','๐Ÿ™‚','๐Ÿ˜Š','๐Ÿ˜‡','๐Ÿฅฐ','๐Ÿ˜','๐Ÿคฉ','๐Ÿ˜˜','๐Ÿ˜‹','๐Ÿ˜›','๐Ÿ˜œ','๐Ÿคช','๐Ÿ˜','๐Ÿค‘','๐Ÿค—','๐Ÿคญ','๐Ÿคซ','๐Ÿค”','๐Ÿค','๐Ÿคจ','๐Ÿ˜','๐Ÿ˜‘','๐Ÿ˜ถ','๐Ÿ˜','๐Ÿ˜’','๐Ÿ™„','๐Ÿ˜ฌ','๐Ÿ˜ฎโ€๐Ÿ’จ','๐Ÿคฅ','๐Ÿ˜Œ','๐Ÿ˜”','๐Ÿ˜ช','๐Ÿคค','๐Ÿ˜ด','๐Ÿ˜ท','๐Ÿค’','๐Ÿค•','๐Ÿคข','๐Ÿคฎ','๐Ÿฅต','๐Ÿฅถ','๐Ÿ˜ต','๐Ÿคฏ','๐Ÿฅณ','๐Ÿฅธ','๐Ÿ˜Ž','๐Ÿค“','๐Ÿง'], gestures: ['๐Ÿ‘','๐Ÿ‘Ž','๐Ÿ‘Œ','๐ŸคŒ','๐Ÿค','โœŒ๏ธ','๐Ÿคž','๐ŸคŸ','๐Ÿค˜','๐Ÿค™','๐Ÿ‘ˆ','๐Ÿ‘‰','๐Ÿ‘†','๐Ÿ‘‡','โ˜๏ธ','โœ‹','๐Ÿคš','๐Ÿ–๏ธ','๐Ÿ––','๐Ÿ‘‹','๐Ÿค','๐Ÿ‘','โœ๏ธ','๐Ÿ™','๐Ÿ’ช','๐Ÿฆพ','๐Ÿซถ','๐Ÿซฑ','๐Ÿซฒ'], hearts: ['โค๏ธ','๐Ÿงก','๐Ÿ’›','๐Ÿ’š','๐Ÿ’™','๐Ÿ’œ','๐Ÿ–ค','๐Ÿค','๐ŸคŽ','๐Ÿ’”','โค๏ธโ€๐Ÿ”ฅ','โค๏ธโ€๐Ÿฉน','๐Ÿ’•','๐Ÿ’ž','๐Ÿ’“','๐Ÿ’—','๐Ÿ’–','๐Ÿ’˜','๐Ÿ’','๐Ÿ’Ÿ','โ™ฅ๏ธ','๐Ÿซ€'], objects: ['๐ŸŽ‰','๐ŸŽŠ','๐ŸŽˆ','๐ŸŽ','๐ŸŽ€','๐Ÿ†','๐Ÿฅ‡','๐ŸŽต','๐ŸŽถ','๐ŸŽธ','๐ŸŽน','๐ŸŽค','๐Ÿ””','โญ','๐ŸŒŸ','๐Ÿ’ซ','โœจ','โšก','๐Ÿ”ฅ','๐Ÿ’ฅ','๐ŸŽฏ','๐Ÿ’ก','๐Ÿ“Œ','๐Ÿ“Ž','โœ…','โŒ','โš ๏ธ','๐Ÿ’ฌ','๐Ÿ’ญ','๐Ÿ—จ๏ธ'], animals: ['๐Ÿถ','๐Ÿฑ','๐Ÿญ','๐Ÿน','๐Ÿฐ','๐ŸฆŠ','๐Ÿป','๐Ÿผ','๐Ÿจ','๐Ÿฏ','๐Ÿฆ','๐Ÿฎ','๐Ÿท','๐Ÿธ','๐Ÿต','๐Ÿ”','๐Ÿง','๐Ÿฆ','๐Ÿฆ…','๐Ÿฆ‰','๐Ÿด','๐Ÿฆ„','๐Ÿ','๐Ÿ›','๐Ÿฆ‹','๐ŸŒ','๐Ÿž','๐Ÿ™','๐Ÿ ','๐Ÿณ'], food: ['๐Ÿ•','๐Ÿ”','๐ŸŸ','๐ŸŒญ','๐Ÿฟ','๐Ÿง€','๐Ÿฅš','๐Ÿณ','๐Ÿฅž','๐Ÿง‡','๐Ÿฅ“','๐Ÿฅฉ','๐Ÿ—','๐Ÿ–','๐ŸŒฎ','๐ŸŒฏ','๐Ÿฅ—','๐Ÿœ','๐Ÿ','๐Ÿฃ','๐Ÿค','๐Ÿฉ','๐Ÿช','๐ŸŽ‚','๐Ÿฐ','๐Ÿง','๐Ÿซ','๐Ÿฌ','๐Ÿญ','โ˜•','๐Ÿต','๐Ÿฅค','๐Ÿงƒ','๐Ÿท','๐Ÿบ'] }; function toggleEmoji() { let picker = document.getElementById('emojiPicker'); picker.classList.toggle('show'); if (picker.classList.contains('show')) renderEmojis('smileys'); } function renderEmojis(cat) { let grid = document.getElementById('emojiGrid'); grid.innerHTML = emojiData[cat].map(e => `
${e}
`).join(''); } function insertEmoji(e) { let input = document.getElementById('messageInput'); input.value += e; input.style.height = 'auto'; input.style.height = (input.scrollHeight) + 'px'; input.focus(); } document.addEventListener('click', function(e) { if(e.target.classList.contains('emoji-tab')) { document.querySelectorAll('.emoji-tab').forEach(t => t.classList.remove('active')); e.target.classList.add('active'); renderEmojis(e.target.dataset.cat); } let picker = document.getElementById('emojiPicker'); let btn = document.getElementById('btnEmoji'); if (picker && btn && picker.classList.contains('show') && !picker.contains(e.target) && !btn.contains(e.target)) { picker.classList.remove('show'); } }); // Context Menu Logic let selectedMsgId = null; let selectedMsgSender = null; let selectedMsgText = null; let replyingToId = null; let editingMsgId = null; let originalEditMsgText = null; // Selection Mode State let isSelectionMode = false; let selectedMessages = []; // Array of objects: {id, sender} function showContextMenu(e, idChat, sender, bubbleEl) { e.preventDefault(); selectedMsgId = idChat; selectedMsgSender = sender; // Extract text ignoring the reply block and the (Diedit) tag let clone = bubbleEl.cloneNode(true); let replyBlocks = clone.querySelectorAll('.msg-reply-block'); replyBlocks.forEach(b => b.remove()); let editTags = clone.querySelectorAll('i'); // Removes the (Diedit) tag along with other icons editTags.forEach(t => t.remove()); selectedMsgText = (clone.textContent || '').trim(); let menu = document.getElementById('msgContextMenu'); // Show/hide Edit button based on sender let btnEdit = document.getElementById('ctxMenuEdit'); if (sender === 'account') { btnEdit.style.display = 'flex'; } else { btnEdit.style.display = 'none'; } let x = e.clientX; let y = e.clientY; if (x + 180 > window.innerWidth) x = window.innerWidth - 190; if (y + 130 > window.innerHeight) y = window.innerHeight - 130; menu.style.left = x + 'px'; menu.style.top = y + 'px'; menu.style.display = 'block'; } function prepareReply(event) { if(event) event.stopPropagation(); replyingToId = selectedMsgId; let title = (selectedMsgSender === 'account') ? 'Membalas Anda' : ('Membalas ' + document.getElementById('activeName').innerText); document.getElementById('rpTitle').innerText = title; document.getElementById('rpText').innerText = selectedMsgText; document.getElementById('replyPreview').style.display = 'block'; document.getElementById('messageInput').focus(); hideContextMenu(); } function closeReplyBox() { replyingToId = null; document.getElementById('replyPreview').style.display = 'none'; } function hideContextMenu() { document.getElementById('msgContextMenu').style.display = 'none'; } document.addEventListener('click', function(e) { hideContextMenu(); }); function deleteSelectedMessage(event) { if(event) event.stopPropagation(); hideContextMenu(); if (!selectedMsgId) return; let msgElement = document.getElementById('msg-' + selectedMsgId); if (selectedMsgSender === 'account') { // Pesan Admin: Ada pilihan Hapus untuk Semua atau Hanya Saya Swal.fire({ title: 'Hapus Pesan?', text: "Pilih metode penghapusan.", icon: 'warning', showDenyButton: true, showCancelButton: true, confirmButtonText: 'Hapus utk Semua', denyButtonText: 'Hapus utk Saya', cancelButtonText: 'Batal', customClass: { popup: 'swal2-custom-small', confirmButton: 'swal-btn-delete-all', denyButton: 'swal-btn-delete-me', cancelButton: 'swal-btn-cancel' }, buttonsStyling: false }).then((result) => { if (result.isConfirmed) { // Semua executeDelete(selectedMsgId, msgElement, 'everyone'); } else if (result.isDenied) { // Saya executeDelete(selectedMsgId, msgElement, 'me'); } }); } else { // Pesan Occupant: Hanya bisa Hapus untuk Saya Swal.fire({ title: 'Hapus Pesan?', text: "Pesan ini hanya terhapus pada layar Anda.", icon: 'warning', showCancelButton: true, confirmButtonText: 'Hapus utk Saya', cancelButtonText: 'Batal', customClass: { popup: 'swal2-custom-small', confirmButton: 'swal-btn-delete-me', cancelButton: 'swal-btn-cancel' }, buttonsStyling: false }).then((result) => { if (result.isConfirmed) { executeDelete(selectedMsgId, msgElement, 'me'); } }); } } function executeDelete(id, msgElement, type) { if (msgElement) msgElement.style.opacity = '0.5'; $.post('chat_api.php', { action: 'delete_message', id_chat: id, delete_type: type }, function(res) { if(res.status === 'success') { $('#msg-' + id).fadeOut(300, function() { $(this).remove(); }); } else { if (msgElement) msgElement.style.opacity = '1'; Swal.fire('Gagal', res.message || 'Pesan tidak dapat dihapus', 'error'); } }, 'json'); } // ======================== // BULK DELETE & SELECTION // ======================== function enterSelectionMode(event) { if(event) event.stopPropagation(); hideContextMenu(); isSelectionMode = true; selectedMessages = [{ id: selectedMsgId, sender: selectedMsgSender }]; // Stop editing/replying if active cancelEdit(); closeReplyBox(); // Switch header actions document.getElementById('normalHeaderActions').style.display = 'none'; document.getElementById('selectionHeaderActions').style.display = 'flex'; document.getElementById('selectionCount').innerText = selectedMessages.length; // Add selectable classes to all rows and selected to the first one let rows = document.querySelectorAll('.msg-row'); rows.forEach(row => { row.classList.add('selectable'); // Add onclick to rows that are currently loaded row.setAttribute('onclick', `toggleSelectMessage(event, ${row.getAttribute('data-id')}, '${row.classList.contains('msg-right') ? 'account' : 'occupant'}')`); }); document.getElementById('msg-' + selectedMsgId).classList.add('selected'); } function exitSelectionMode() { isSelectionMode = false; selectedMessages = []; // Switch header actions back document.getElementById('normalHeaderActions').style.display = 'block'; document.getElementById('selectionHeaderActions').style.display = 'none'; // Remove selectable classes let rows = document.querySelectorAll('.msg-row'); rows.forEach(row => { row.classList.remove('selectable', 'selected'); row.removeAttribute('onclick'); // Important: remove the bulk select onclick }); } function toggleSelectMessage(event, idChat, sender) { if(event) event.stopPropagation(); if(!isSelectionMode) return; let index = selectedMessages.findIndex(m => m.id === idChat); let row = document.getElementById('msg-' + idChat); if (index > -1) { // Deselect selectedMessages.splice(index, 1); if(row) row.classList.remove('selected'); } else { // Select selectedMessages.push({ id: idChat, sender: sender }); if(row) row.classList.add('selected'); } document.getElementById('selectionCount').innerText = selectedMessages.length; // If no messages selected, automatically exit selection mode if (selectedMessages.length === 0) { exitSelectionMode(); } } function triggerBulkDelete() { if (selectedMessages.length === 0) return; // Check if ALL selected messages are from admin let onlyAdminMessages = selectedMessages.every(m => m.sender === 'account'); if (onlyAdminMessages) { // Admin only: Can choose 'Untuk Semua' or 'Hanya Saya' Swal.fire({ title: 'Hapus ' + selectedMessages.length + ' Pesan?', text: "Pilih metode penghapusan.", icon: 'warning', showDenyButton: true, showCancelButton: true, confirmButtonText: 'Hapus utk Semua', denyButtonText: 'Hapus utk Saya', cancelButtonText: 'Batal', customClass: { popup: 'swal2-custom-small', confirmButton: 'swal-btn-delete-all', denyButton: 'swal-btn-delete-me', cancelButton: 'swal-btn-cancel' }, buttonsStyling: false }).then((result) => { if (result.isConfirmed) { executeBulkDelete('everyone'); } else if (result.isDenied) { executeBulkDelete('me'); } }); } else { // Mix of Admin + Occupant OR Occupant only: Only 'Hanya Saya' Swal.fire({ title: 'Hapus ' + selectedMessages.length + ' Pesan?', text: "Pesan-pesan ini hanya terhapus pada layar Anda.", icon: 'warning', showCancelButton: true, confirmButtonText: 'Hapus', cancelButtonText: 'Batal', customClass: { popup: 'swal2-custom-small', confirmButton: 'swal-btn-delete-me', cancelButton: 'swal-btn-cancel' }, buttonsStyling: false }).then((result) => { if (result.isConfirmed) { executeBulkDelete('me'); } }); } } function executeBulkDelete(type) { let idsToDelete = selectedMessages.map(m => m.id); // Dim the UI immediately idsToDelete.forEach(id => { let el = document.getElementById('msg-' + id); if(el) el.style.opacity = '0.5'; }); $.post('chat_api.php', { action: 'bulk_delete_messages', id_chats: idsToDelete, delete_type: type }, function(res) { if(res.status === 'success') { idsToDelete.forEach(id => { $('#msg-' + id).fadeOut(300, function() { $(this).remove(); }); }); exitSelectionMode(); Swal.fire({ title: 'Terhapus!', text: res.message, icon: 'success', timer: 1500, showConfirmButton: false }); } else { // Revert opacity idsToDelete.forEach(id => { let el = document.getElementById('msg-' + id); if(el) el.style.opacity = '1'; }); Swal.fire('Gagal', res.message || 'Gagal menghapus beberapa pesan', 'error'); } }, 'json'); } function editSelectedMessage(event) { if(event) event.stopPropagation(); hideContextMenu(); if (!selectedMsgId || selectedMsgSender !== 'account') return; editingMsgId = selectedMsgId; originalEditMsgText = selectedMsgText; // Set UI to Edit Mode document.getElementById('epText').innerText = originalEditMsgText; document.getElementById('editPreview').style.display = 'block'; // If user was replying, cancel it so they don't do both closeReplyBox(); let inputEl = document.getElementById('messageInput'); inputEl.value = originalEditMsgText; inputEl.style.height = 'auto'; inputEl.style.height = (inputEl.scrollHeight) + 'px'; inputEl.focus(); } function cancelEdit() { editingMsgId = null; originalEditMsgText = null; document.getElementById('editPreview').style.display = 'none'; let inputEl = document.getElementById('messageInput'); inputEl.value = ''; inputEl.style.height = 'auto'; } function saveEdit() { let input = document.getElementById('messageInput'); let newText = input.value.trim(); if (!newText || !editingMsgId) return; if (newText === originalEditMsgText) { cancelEdit(); return; } let msgElement = document.getElementById('msg-' + editingMsgId); if (msgElement) msgElement.style.opacity = '0.5'; let idToEdit = editingMsgId; cancelEdit(); // Reset UI immediately $.post('chat_api.php', { action: 'edit_message', id_chat: idToEdit, new_message: newText }, function(res) { if (msgElement) msgElement.style.opacity = '1'; if (res.status === 'success') { // Reload messages to ensure formatting consistency if (activeOccupantId) { $.post('chat_api.php', { action: 'get_messages', id_occupant: activeOccupantId }, function(res2) { if(res2.status === 'success') { renderMessages(res2.data); } }, 'json'); } } else { Swal.fire('Gagal', res.message || 'Pesan tidak dapat diedit', 'error'); } }, 'json'); } // Initial load $(document).ready(function() { loadContacts(); renderEmojis('smileys'); setInterval(function(){ if(isSelectionMode) return; // DON'T REFRESH HTML WHILE USER IS SELECTING if(!document.getElementById('searchInput').value) { loadContacts(); if(activeOccupantId) { $.post('chat_api.php', { action: 'get_messages', id_occupant: activeOccupantId }, function(res2) { if(res2.status === 'success') { let hist = document.getElementById('chatHistory'); let newHtml = buildMessagesHtml(res2.data); if(hist.innerHTML !== newHtml) { let isScrolledBottom = hist.scrollHeight - hist.clientHeight <= hist.scrollTop + 10; // Update innerHTML instead of appending hist.innerHTML = newHtml; if(isScrolledBottom) scrollToBottom(); } } }, 'json'); } } }, 5000); });