Update conversations.php

This commit is contained in:
Lizandro Guarnizo
2026-01-24 00:36:22 -05:00
parent 2dc0035c3c
commit 7b2e17c740
+141 -24
View File
@@ -665,14 +665,13 @@
border-radius: 4px;
}
.reaction-badge {
display: inline-block;
background: rgba(0,0,0,0.06);
padding: 2px 6px;
border-radius: 12px;
font-size: 12px;
margin-top: 8px;
}
.reaction-badge { background:#fff; border:1px solid rgba(0,0,0,0.06); display:inline-flex; align-items:center; justify-content:center; padding:4px 6px; border-radius:14px; font-size:13px; margin-top:6px; }
/* Reaction picker */
#reaction-picker { position:fixed; display:none; z-index:2000; background:#fff; border-radius:10px; box-shadow:0 6px 24px rgba(0,0,0,0.16); padding:8px; min-width:160px; transition:transform .12s ease, opacity .12s ease; transform:scale(.96); opacity:0; }
#reaction-picker.show { transform:scale(1); opacity:1; }
#reaction-picker .emoji { font-size:18px; padding:6px; cursor:pointer; border-radius:6px; margin:4px; display:inline-flex; align-items:center; justify-content:center; }
#reaction-picker .emoji:hover { background: rgba(0,0,0,0.04); }
.conversation-avatar img, .conversation-avatar-img { max-width: 44px; max-height:44px; border-radius:50%; }
.conversation-avatar-wrapper { width:48px; display:flex; align-items:center; justify-content:center }
@@ -755,7 +754,7 @@
<button class="btn btn-sm btn-outline-secondary" id="bot-toggle">Bot: On</button>
</div>
<button class="btn btn-sm btn-outline-secondary" id="mark-unread-btn" title="Marcar conversación como no leída" style="display:none;">Marcar no leído</button>
<button class="btn btn-sm btn-outline-danger" id="delete-conversation-btn" title="Eliminar conversación"><i class="fas fa-trash"></i></button>
<button class="btn btn-sm btn-outline-danger" id="delete-conversation-btn" title="Eliminar conversación" style="display:none;"><i class="fas fa-trash"></i></button>
</div>
</div>
@@ -1107,6 +1106,20 @@
this.sendMessage();
});
// Forward handler: open preview when user chooses to forward an existing message
this.forwardMessage = function(messageId) {
try {
const input = document.getElementById('message-input');
if (input) {
input.dataset.forwardTo = messageId;
}
this.showReplyPreview(messageId, 'forward');
// Optionally focus the recipient selection by showing a hint or opening the sidebar
} catch (e) {
console.warn('forwardMessage failed', e);
}
};
// Edit user name button
const editUserBtn = document.getElementById('edit-user-btn');
if (editUserBtn) editUserBtn.addEventListener('click', () => this.promptEditUser());
@@ -1116,19 +1129,77 @@
const input = document.getElementById('message-input');
if (!input) return;
input.dataset.replyTo = messageId;
this.showReplyPreview(messageId);
input.dataset.forwardTo = null;
this.showReplyPreview(messageId, 'reply');
input.focus();
};
// React to message placeholder
this.reactToMessage = function(messageId) {
// simple emoji picker or quick reaction; for now show a prompt
const e = prompt('Reaccionar con emoji (ej: 👍):');
if (!e) return;
fetch('api/react_message.php', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ message_id: messageId, emoji: e }) })
.then(r => r.json()).then(j => { if (j && j.success) { this.loadconversations(this.currentUserId, false); } else { alert('Error reaccionando'); } }).catch(err => { console.error(err); alert('Error reaccionando'); });
// Reaction picker: open a small emoji palette near the click
this.openReactionPicker = function(ev, messageId) {
if (ev && ev.stopPropagation) ev.stopPropagation();
const picker = document.getElementById('reaction-picker');
if (!picker) return;
this._pendingReactionMessage = messageId;
// position picker near event or center
picker.style.display = 'block';
picker.classList.remove('show');
// compute position
let left = (window.innerWidth / 2) - (picker.offsetWidth / 2);
let top = (window.innerHeight / 2) - (picker.offsetHeight / 2);
if (ev && ev.clientX) {
left = Math.min(Math.max(8, ev.clientX - 40), window.innerWidth - picker.offsetWidth - 8);
top = Math.min(Math.max(8, ev.clientY - 24), window.innerHeight - picker.offsetHeight - 8);
}
picker.style.left = left + 'px';
picker.style.top = top + 'px';
// small delay to allow CSS transition
setTimeout(() => picker.classList.add('show'), 10);
};
// Hide reaction picker on document click
document.addEventListener('click', (ev) => {
const picker = document.getElementById('reaction-picker');
if (picker && picker.style.display === 'block' && !picker.contains(ev.target)) {
picker.classList.remove('show');
setTimeout(()=>{ picker.style.display='none'; },160);
}
});
// Attach click handler for emojis (event delegation)
document.addEventListener('click', async (ev) => {
const t = ev.target;
if (t && t.dataset && t.dataset.emoji) {
ev.stopPropagation();
const emoji = t.dataset.emoji;
const mid = this._pendingReactionMessage;
if (!mid) return;
try {
const resp = await fetch('api/react_message.php', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ message_id: mid, emoji: emoji }) });
const j = await resp.json();
if (j && j.success) {
// update local model and UI
const m = this.conversations.find(x => x.message_id == mid || x.id == mid);
if (m) m.reaction_emoji = emoji;
this.updateMessageReactionInView && this.updateMessageReactionInView(mid, emoji);
} else {
showAlert && showAlert('Error aplicando reacción', 'danger');
}
} catch (err) {
console.error('Reaction error', err);
showAlert && showAlert('Error aplicando reacción', 'danger');
} finally {
const picker = document.getElementById('reaction-picker');
if (picker) {
picker.classList.remove('show');
setTimeout(()=>{ picker.style.display='none'; },160);
}
this._pendingReactionMessage = null;
}
}
});
// Message type (text/template)
const typeSelect = document.getElementById('message-type');
if (typeSelect) {
@@ -2047,14 +2118,15 @@
}
return `
<div class="message ${msg.direction}">
<div class="message ${msg.direction}" data-message-id="${escapeHtml(msg.message_id || msg.id || '')}">
<div class="message-bubble">
${replyHtml}
<div class="message-content">${content}</div>
${reactionHtml}
<div class="message-actions mt-1">
<button class="btn btn-sm btn-link" onclick="chat.promptReply('${msg.message_id}')" title="Responder"><i class="fas fa-reply"></i></button>
<button class="btn btn-sm btn-link" onclick="chat.reactToMessage('${msg.message_id}')" title="Reaccionar"><i class="far fa-grin"></i></button>
<button class="btn btn-sm btn-link" onclick="chat.forwardMessage('${msg.message_id}')" title="Reenviar"><i class="fas fa-share"></i></button>
<button class="btn btn-sm btn-link" onclick="chat.openReactionPicker(event, '${msg.message_id}')" title="Reaccionar"><i class="far fa-grin"></i></button>
</div>
<div class="message-time">
${time}
@@ -2077,6 +2149,32 @@
}
}
// Update reaction badge for a single message in the DOM
updateMessageReactionInView(messageId, emoji) {
try {
const container = document.getElementById('chat-conversations');
if (!container) return;
const el = container.querySelector(`[data-message-id="${messageId}"]`);
if (!el) return;
let badge = el.querySelector('.reaction-badge');
if (emoji) {
if (badge) {
badge.textContent = emoji;
} else {
const div = document.createElement('div');
div.className = 'reaction-badge';
div.textContent = emoji;
const bubble = el.querySelector('.message-bubble');
if (bubble) bubble.insertBefore(div, bubble.querySelector('.message-actions'));
}
} else {
if (badge && badge.parentNode) badge.parentNode.removeChild(badge);
}
} catch (e) {
console.warn('updateMessageReactionInView failed', e);
}
}
async loadQuickReplies() {
const panel = document.getElementById('quick-replies-panel');
if (!panel) return;
@@ -2200,15 +2298,16 @@
this.scrollToBottom();
}
showReplyPreview(messageId) {
showReplyPreview(messageId, type = 'reply') {
try {
const preview = document.getElementById('reply-preview');
const previewText = document.getElementById('reply-preview-text');
const cancelBtn = document.getElementById('cancel-reply-btn');
const msg = this.conversations.find(m => m.message_id == messageId);
const text = msg ? (msg.content || msg.message_text || '[Mensaje]') : ('Mensaje ' + messageId);
const msg = this.conversations.find(m => m.message_id == messageId || m.id == messageId);
const text = msg ? (msg.content || msg.message_text || (msg.caption || '[Mensaje]')) : ('Mensaje ' + messageId);
if (preview && previewText) {
previewText.textContent = 'En respuesta a: ' + (String(text).replace(/\n/g,' ')).substring(0,140);
if (type === 'reply') previewText.textContent = 'En respuesta a: ' + (String(text).replace(/\n/g,' ')).substring(0,140);
else if (type === 'forward') previewText.textContent = 'Reenviando: ' + (String(text).replace(/\n/g,' ')).substring(0,140);
preview.style.display = 'flex';
}
if (cancelBtn) {
@@ -2224,7 +2323,10 @@
const preview = document.getElementById('reply-preview');
if (preview) preview.style.display = 'none';
const input = document.getElementById('message-input');
if (input) input.dataset.replyTo = null;
if (input) {
input.dataset.replyTo = null;
input.dataset.forwardTo = null;
}
} catch (e) {
console.warn('hideReplyPreview failed', e);
}
@@ -2747,5 +2849,20 @@
</div>
</div>
<!-- Reaction picker -->
<div id="reaction-picker" aria-hidden="true">
<div style="display:flex; flex-wrap:wrap; gap:6px;">
<button class="emoji" data-emoji="👍">👍</button>
<button class="emoji" data-emoji="❤️">❤️</button>
<button class="emoji" data-emoji="😂">😂</button>
<button class="emoji" data-emoji="😮">😮</button>
<button class="emoji" data-emoji="😢">😢</button>
<button class="emoji" data-emoji="👏">👏</button>
<button class="emoji" data-emoji="🎉">🎉</button>
<button class="emoji" data-emoji="🔥">🔥</button>
<button class="emoji" data-emoji="🙏">🙏</button>
</div>
</div>
</body>
</html>