This commit is contained in:
Lizandro Guarnizo
2026-01-23 22:12:27 -05:00
parent d9942456c8
commit 2017c8c805
8 changed files with 420 additions and 110 deletions
+110 -11
View File
@@ -73,11 +73,73 @@ if (empty($user_id)) {
.message-bubble {
margin: 6px 0;
padding: 6px 10px; /* menos padding */
border-radius: 12px;
max-width: 70%;
border-radius: 18px;
max-width: 78%;
word-wrap: break-word;
font-size: 0.88rem;
position: relative; /* para posicionar timestamp */
padding-right: 60px; /* espacio para timestamp y ticks */
}
/* Estilos tipo WhatsApp: colas y timestamps dentro de burbuja */
.message-bubble.message-outgoing {
border-bottom-right-radius: 4px; /* más afilado en la esquina del tail */
}
.message-bubble.message-incoming {
border-bottom-left-radius: 4px;
}
/* Cola simple usando pseudo-elemento */
.message-bubble.message-outgoing::after {
content: '';
position: absolute;
right: -6px;
bottom: 4px;
width: 12px;
height: 12px;
background: #DCF8C6;
transform: rotate(45deg);
border-bottom-right-radius: 2px;
z-index: 0;
}
.message-bubble.message-incoming::after {
content: '';
position: absolute;
left: -6px;
bottom: 4px;
width: 12px;
height: 12px;
background: #fff;
transform: rotate(45deg);
border-bottom-left-radius: 2px;
z-index: 0;
box-shadow: 0 1px 2px rgba(0,0,0,0.03);
}
/* Timestamp dentro de la burbuja, oculto hasta hover para limpieza visual */
.message-bubble .message-time {
position: absolute;
right: 8px;
bottom: 4px;
font-size: 0.65rem;
color: #666;
opacity: 0;
transition: opacity 0.12s ease-in-out;
white-space: nowrap;
}
.message-bubble:hover .message-time,
.message-bubble:focus-within .message-time {
opacity: 1;
}
/* Agrupación de mensajes consecutivos (menor separación) */
.message-bubble.grouped { margin-top: 2px; }
.message-bubble.grouped + .message-bubble { margin-top: 2px; }
.message-bubble.grouped::after { bottom: 2px; }
.message-bubble.message-alert { padding-right: 60px; }
/* Ajuste para que texto no choque con timestamp */
.message-text { display: block; padding-right: 6px; }
/* Estilos para mensajes multimedia */
.message-bubble img {
@@ -150,6 +212,13 @@ if (empty($user_id)) {
.message-incoming .message-time {
text-align: left;
}
/* Preservar espacios y saltos de línea en el texto de mensajes */
.message-text {
white-space: pre-wrap; /* conserva saltos de línea y múltiples espacios */
word-break: break-word;
overflow-wrap: anywhere;
}
.typing-indicator {
display: none;
@@ -772,9 +841,23 @@ if (empty($user_id)) {
}
let html = '';
conversations.forEach(msg => {
for (let i = 0; i < conversations.length; i++) {
const msg = conversations[i];
const prev = conversations[i-1] || null;
const isOutgoing = msg.direction === 'outgoing';
let messageClass = isOutgoing ? 'message-outgoing' : 'message-incoming';
// Agrupar si el mensaje anterior es del mismo lado y está cerca en el tiempo (5 min)
try {
if (prev && prev.direction === msg.direction) {
const t1 = new Date(prev.created_at).getTime();
const t2 = new Date(msg.created_at).getTime();
if (!isNaN(t1) && !isNaN(t2) && (Math.abs(t2 - t1) <= (5 * 60 * 1000))) {
messageClass += ' grouped';
}
}
} catch (e) {
// ignore parsing errors
}
// Debug: ver todos los datos del mensaje (prioriza media_url_external y valida URLs)
const has_media_external = isValidMediaUrl(msg.media_url_external);
@@ -974,29 +1057,45 @@ if (empty($user_id)) {
}
if (alertInfo) {
contentHtml = `<div class="fw-bold">${escapeHtml(alertInfo.text || '[Atención]')}</div>`;
contentHtml = `<div class="message-text fw-bold">${escapeHtml(alertInfo.text || '[Atención]')}</div>`;
// Añadir clase especial a la burbuja
// marca el mensaje como requiring attention mediante messageClass
// we'll append an additional class later
messageClass += ' message-alert';
} else {
contentHtml = escapeHtml(msg.content) || '<em>Sin contenido</em>';
const textContent = msg.content || '';
contentHtml = textContent ? `<div class="message-text">${escapeHtml(textContent)}</div>` : '<em>Sin contenido</em>';
}
}
const messageTime = escapeHtml(msg.time || '');
// Status ticks (solo en salientes)
let statusHtml = '';
if (isOutgoing) {
const s = (msg.status || '').toLowerCase();
if (s === 'read' || s === 'seen') {
statusHtml = '<i class="fas fa-check-double text-primary" title="Leído"></i>';
} else if (s === 'delivered') {
statusHtml = '<i class="fas fa-check-double text-muted" title="Entregado"></i>';
} else if (s === 'sent') {
statusHtml = '<i class="fas fa-check text-muted" title="Enviado"></i>';
} else {
statusHtml = '';
}
}
html += `
<div class="d-flex ${isOutgoing ? 'justify-content-end' : 'justify-content-start'}">
<div class="message-bubble ${messageClass}">
<div class="message-content">${contentHtml}</div>
<div class="message-time">
${messageTime} ${isOutgoing ? '<i class="fas fa-check-double text-primary"></i>' : ''}
${messageTime} ${statusHtml}
</div>
</div>
</div>
`;
});
}
chatconversations.innerHTML = html;
scrollToBottom();
@@ -1167,13 +1266,13 @@ if (empty($user_id)) {
const messageHtml = `
<div class="d-flex ${isOutgoing ? 'justify-content-end' : 'justify-content-start'}">
<div class="message-bubble ${messageClass}">
<div class="message-content">${escapeHtml(message)}</div>
<div class="message-content"><div class="message-text">${escapeHtml(message)}</div></div>
<div class="message-time">
${now} ${isOutgoing ? '<i class="fas fa-check text-muted"></i>' : ''}
</div>
</div>
</div>
`;
`;
chatconversations.insertAdjacentHTML('beforeend', messageHtml);
scrollToBottom();
@@ -1723,7 +1822,7 @@ if (empty($user_id)) {
let html = `<div class="message-media">${mediaHtml}</div>`;
if (message.caption) {
html += `<div style="margin-top: 5px;">${escapeHtml(message.caption)}</div>`;
html += `<div class="message-text" style="margin-top: 5px;">${escapeHtml(message.caption)}</div>`;
}
if (timestamp) {