Update conversations.php

This commit is contained in:
Lizandro Guarnizo
2026-01-24 01:34:18 -05:00
parent ff1e03d03c
commit 8e0388a7a7
+118 -3
View File
@@ -255,6 +255,17 @@
white-space: -o-pre-wrap;
word-break: break-word;
}
/* WhatsApp-like inline formatting and interactive messages */
.wa-interactive { margin-top: 8px; border: 1px solid rgba(0,0,0,0.06); padding: 8px; border-radius: 8px; background: #fff; }
.wa-interactive .wa-title { font-weight:700; margin-bottom:6px; }
.wa-interactive .wa-buttons { display:flex; gap:6px; flex-wrap:wrap; }
.wa-interactive .wa-buttons .wa-interactive-btn { white-space:nowrap; }
.wa-interactive .wa-list { margin-top:6px; }
.wa-interactive .wa-list-item { padding:8px; border-radius:6px; cursor:pointer; border:1px solid transparent; }
.wa-interactive .wa-list-item:hover { background:#f6f8fb; border-color:rgba(0,0,0,0.04); }
.wa-code { background:#0b0b0b; color:#fff; padding:8px; border-radius:6px; font-family: monospace; white-space: pre-wrap; }
.wa-inline-code { background:#f4f4f4; padding:2px 6px; border-radius:4px; font-family: monospace; }
.message-bubble:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(2,6,23,0.06);
@@ -818,11 +829,11 @@
<option value="text">Texto</option>
<option value="template">Plantilla</option>
</select> -->
<div id="templateSelectContainer" style="display:none; margin-right:8px;">
<!-- <div id="templateSelectContainer" style="display:none; margin-right:8px;">
<select id="templateSelect" class="form-select form-select-sm">
<option value="">Seleccionar plantilla...</option>
</select>
</div>
</div> -->
<div class="quick-replies-wrapper" style="position:relative;">
<button id="quick-replies-toggle" class="btn btn-sm btn-outline-secondary" title="Respuestas rápidas" aria-expanded="false">⚡️</button>
<div id="quick-replies-panel" class="quick-replies-panel" style="display:none; position:absolute; bottom:48px; left:0; z-index:1200; min-width:220px; max-width:420px;">
@@ -2328,7 +2339,9 @@
replyHtml = `<div class="reply-preview">En respuesta a: ${window.escapeHtml(previewText)}</div>`;
}
const mediaPresent = (msg.local_thumb || msg.local_file || msg.media_url_external || msg.media_url);
const content = mediaPresent ? (window.renderMediaMessage ? window.renderMediaMessage(msg) : (window.escapeHtml(msg.content || '[Mensaje]'))) : window.escapeHtml(msg.content || '[Mensaje vacío]');
const content = mediaPresent
? (window.renderMediaMessage ? window.renderMediaMessage(msg) : (window.formatMessageContent ? window.formatMessageContent(msg.content || '[Mensaje]') : window.escapeHtml(msg.content || '[Mensaje]')))
: (window.formatMessageContent ? window.formatMessageContent(msg.content || '[Mensaje vacío]') : window.escapeHtml(msg.content || '[Mensaje vacío]'));
const time = msg.created_at ? new Date(msg.created_at).toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' }) : '';
const statusIcon = this.getStatusIcon(msg.status);
const reactionHtml = msg.reaction_emoji ? `<div class="reaction-badge">${msg.reaction_emoji}</div>` : '';
@@ -3181,6 +3194,108 @@
}
}
// Helper para renderizar WhatsApp-like formatted text and interactive messages
function renderInteractiveMessage(obj) {
try {
const interactive = obj.interactive || obj;
const t = interactive.type || (interactive.header && 'button');
if (t === 'button' || interactive.button) {
const title = escapeHtml(interactive.title || interactive.body || '');
const buttons = interactive.buttons || interactive.button || [];
const html = [`<div class="wa-interactive">`, title ? `<div class="wa-title">${title}</div>` : ''];
html.push(`<div class="wa-buttons">`);
buttons.forEach(b => {
const label = escapeHtml(b.title || b.text || b.body || b.label || '');
html.push(`<button class="btn btn-sm btn-outline-primary wa-interactive-btn" data-value="${label}">${label}</button>`);
});
html.push(`</div></div>`);
return html.join('');
}
if (t === 'list' || interactive.sections) {
const title = escapeHtml(interactive.title || interactive.header || '');
const sections = interactive.sections || [];
const html = [`<div class="wa-interactive">`, title ? `<div class="wa-title">${title}</div>` : ''];
html.push(`<div class="wa-list">`);
sections.forEach(s => {
const secTitle = escapeHtml(s.title || '');
if (secTitle) html.push(`<div class="wa-section-title">${secTitle}</div>`);
(s.rows || []).forEach(r => {
const rowTitle = escapeHtml(r.title || r.name || r.id || '');
const rowDesc = escapeHtml(r.description || '');
html.push(`<div class="wa-list-item" data-value="${rowTitle}"><div class="wa-list-name">${rowTitle}</div>${rowDesc?`<div class="wa-list-desc small text-muted">${rowDesc}</div>`:''}</div>`);
});
});
html.push(`</div></div>`);
return html.join('');
}
} catch (e) { console.warn('renderInteractiveMessage failed', e); }
return '';
}
function formatMessageInlineCode(s) {
return s.replace(/`([^`]+)`/g, (m, g1) => `<span class="wa-inline-code">${escapeHtml(g1)}</span>`);
}
function formatMessageContent(text) {
if (!text && text !== 0) return '';
try {
// If looks like JSON with interactive payload, render interactive UI
const t = String(text).trim();
if ((t.startsWith('{') && t.endsWith('}')) || (t.startsWith('[') && t.endsWith(']'))) {
try {
const parsed = JSON.parse(t);
if (parsed && (parsed.interactive || parsed.type === 'interactive')) {
return renderInteractiveMessage(parsed.interactive || parsed);
}
} catch (e) { /* not json */ }
}
// Escape HTML first
let s = escapeHtml(String(text));
// Code block (triple backticks) -> <pre>
s = s.replace(/```([\s\S]*?)```/g, (m, g1) => `<div class="wa-code">${escapeHtml(g1)}</div>`);
// Inline code
s = formatMessageInlineCode(s);
// Bold *text*
s = s.replace(/\*([^*]+)\*/g, '<strong>$1</strong>');
// Italic _text_
s = s.replace(/_([^_]+)_/g, '<em>$1</em>');
// Strikethrough ~text~
s = s.replace(/~([^~]+)~/g, '<del>$1</del>');
// Auto-link URLs
s = s.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>');
return s;
} catch (e) {
console.warn('formatMessageContent failed', e);
return escapeHtml(String(text));
}
}
window.formatMessageContent = formatMessageContent;
// Delegate clicks on interactive buttons/list items to send quick replies
document.addEventListener('click', (ev) => {
const btn = ev.target.closest('.wa-interactive-btn');
if (btn) {
ev.stopPropagation();
const v = btn.dataset.value;
if (v && window.chatApp && typeof window.chatApp.sendQuickReply === 'function') {
window.chatApp.sendQuickReply(v);
}
}
const item = ev.target.closest('.wa-list-item');
if (item) {
ev.stopPropagation();
const v = item.dataset.value;
if (v && window.chatApp && typeof window.chatApp.sendQuickReply === 'function') {
window.chatApp.sendQuickReply(v);
}
}
});
// Helper para iconos de archivos (copiado de chat_window)
function getFileIcon(filename) {
if (!filename) return 'fas fa-file text-muted';