Update conversations.php
This commit is contained in:
+118
-3
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user