Update conversations.php

This commit is contained in:
Lizandro Guarnizo
2026-01-26 00:45:25 -05:00
parent dea1e170a5
commit bb311bd043
+144 -5
View File
@@ -236,15 +236,28 @@
box-shadow: 0 1px 2px rgba(0,0,0,0.04);
}
/* Timestamp badge shown at the corner of each bubble (WhatsApp-like) */
.message-bubble { padding-bottom: 20px; }
.message-bubble .message-time {
position: absolute;
right: 8px;
bottom: 4px;
font-size: 10px;
bottom: 6px;
font-size: 11px;
line-height: 1;
color: #666;
opacity: 0;
transition: opacity 0.12s ease-in-out;
opacity: 1; /* always visible */
transition: opacity 0.12s ease-in-out, transform 0.12s ease;
white-space: nowrap;
font-weight: 500;
background: transparent;
padding: 0 4px;
}
/* Different color for outgoing (light text over green bubble) */
.message.outgoing .message-bubble .message-time { color: rgba(255,255,255,0.92); }
.message.incoming .message-bubble .message-time { color: rgba(32,37,41,0.7); }
/* reduce prominence on very small screens */
@media (max-width:420px) {
.message-bubble .message-time { font-size:10px; right:6px; bottom:4px; }
}
/* Preserve user whitespace and line breaks like WhatsApp */
@@ -608,6 +621,19 @@
transform-origin: center left;
animation: pulseHighlight 1s ease both;
}
/* Date separator between message days */
.date-sep {
text-align: center;
font-size: 12px;
color: #666;
padding: 6px 0;
margin: 12px 0;
position: relative;
}
.date-sep::before, .date-sep::after { content: ''; display: inline-block; vertical-align: middle; width: 20%; height: 1px; background: rgba(0,0,0,0.06); margin: 0 8px; }
.date-sep { background: transparent; font-weight:600; }
@keyframes pulseHighlight {
0% { transform: translateY(6px) scale(.995); opacity: 0.95; }
50% { transform: translateY(0) scale(1.01); opacity: 1; }
@@ -756,6 +782,10 @@
.unread-badge { margin-left: 8px; font-size: 12px; }
</style>
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#10b981">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
</head>
<body>
<div class="chat-container">
@@ -830,6 +860,8 @@
<div id="bot-toggle-container" style="display:flex;align-items:center;gap:6px;margin-right:8px;">
<button class="btn btn-sm btn-outline-secondary" id="bot-toggle">Bot: On</button>
</div>
<!-- PWA install button for conversations view -->
<button id="pwa-install-btn" class="btn btn-sm btn-outline-primary d-none" style="display:none;">Instalar</button>
<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" style="display:none;"><i class="fas fa-trash"></i></button>
</div>
@@ -1656,6 +1688,27 @@
};
applyInitialMobile();
window.addEventListener('resize', applyInitialMobile);
// PWA support (install prompt + SW)
let _deferredPWA = null;
const _pwaBtnConv = document.getElementById('pwa-install-btn');
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
_deferredPWA = e;
if (_pwaBtnConv) { _pwaBtnConv.style.display = 'inline-block'; _pwaBtnConv.classList.remove('d-none'); }
});
if (_pwaBtnConv) {
_pwaBtnConv.addEventListener('click', async () => {
if (!_deferredPWA) return;
_deferredPWA.prompt();
const choice = await _deferredPWA.userChoice;
console.debug('PWA install (conv):', choice);
if (choice && choice.outcome === 'accepted') _pwaBtnConv.style.display = 'none';
_deferredPWA = null;
});
}
window.addEventListener('appinstalled', () => console.debug('PWA installed (conversations)'));
if ('serviceWorker' in navigator) navigator.serviceWorker.register('/service-worker.js').then(() => console.debug('SW reg (conv)')).catch(e => console.warn('SW fail', e));
}
const stopRecBtn = document.getElementById('stop-recording');
@@ -2701,6 +2754,27 @@
existing.set(String(el.dataset.messageId), el);
});
// helper: format date separators and compare same day
const sameDay = (a,b) => {
try {
const da = new Date(a); const db = new Date(b);
return da.getFullYear() === db.getFullYear() && da.getMonth() === db.getMonth() && da.getDate() === db.getDate();
} catch (e) { return false; }
};
const formatDateLabel = (d) => {
try {
const date = new Date(d);
const today = new Date();
const y = new Date(); y.setDate(today.getDate()-1);
if (sameDay(date, today)) return 'Hoy';
if (sameDay(date, y)) return 'Ayer';
// within last 7 days -> weekday name
const diff = Math.floor((today - date) / (1000*60*60*24));
if (diff < 7) return date.toLocaleDateString('es-ES', { weekday: 'long', day: '2-digit', month: '2-digit' });
return date.toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit', year: 'numeric' });
} catch(e) { return ''; }
};
const prependEls = []; // elements to insert at top if we loaded older messages
// calculate near-bottom late to decide scrolling after DOM updates (avoids stale value)
@@ -2787,7 +2861,34 @@
}
const timeEl = existingEl.querySelector('.message-time');
if (timeEl) timeEl.textContent = msg.created_at ? new Date(msg.created_at).toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' }) : '';
if (timeEl && msg.created_at) {
const full = new Date(msg.created_at).toLocaleString('es-ES');
const short = new Date(msg.created_at).toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' });
timeEl.textContent = short;
timeEl.dataset.full = full;
timeEl.dataset.short = short;
if (!timeEl._hasToggleListener) {
timeEl.addEventListener('click', (e) => {
e.stopPropagation();
if (timeEl.dataset._toggled === '1') {
timeEl.textContent = timeEl.dataset.short;
timeEl.dataset._toggled = '0';
} else {
timeEl.textContent = timeEl.dataset.full;
timeEl.dataset._toggled = '1';
setTimeout(() => {
if (timeEl.dataset._toggled === '1') {
timeEl.textContent = timeEl.dataset.short;
timeEl.dataset._toggled = '0';
}
}, 4000);
}
});
timeEl._hasToggleListener = true;
}
} else if (timeEl) {
timeEl.textContent = '';
}
const statusEl = existingEl.querySelector('.message-status');
if (statusEl) statusEl.innerHTML = this.getStatusIcon(msg.status);
@@ -2803,6 +2904,17 @@
} else {
// create new element
try {
// insert date separator if this message is the first of a new day
const prev = (i>0) ? this.conversations[i-1] : null;
if (!prev || !sameDay(prev.created_at, msg.created_at)) {
const sep = document.createElement('div');
sep.className = 'date-sep';
sep.textContent = formatDateLabel(msg.created_at || Date.now());
// For prepended older messages, collect to insert at top
if (prependCount && i < prependCount) prependEls.push(sep);
else container.appendChild(sep);
}
const div = document.createElement('div');
div.className = 'message ' + (msg.direction || 'incoming');
div.dataset.messageId = mid;
@@ -2907,6 +3019,33 @@
container.appendChild(div);
}
// attach small behavior: allow tapping the time to reveal full date/time briefly
try {
const timeEl = div.querySelector('.message-time');
if (timeEl && msg.created_at) {
const full = new Date(msg.created_at).toLocaleString('es-ES');
const short = new Date(msg.created_at).toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' });
timeEl.dataset.full = full;
timeEl.dataset.short = short;
timeEl.addEventListener('click', (e) => {
e.stopPropagation();
if (timeEl.dataset._toggled === '1') {
timeEl.textContent = timeEl.dataset.short;
timeEl.dataset._toggled = '0';
} else {
timeEl.textContent = timeEl.dataset.full;
timeEl.dataset._toggled = '1';
setTimeout(() => {
if (timeEl.dataset._toggled === '1') {
timeEl.textContent = timeEl.dataset.short;
timeEl.dataset._toggled = '0';
}
}, 4000);
}
});
}
} catch (e) { /* ignore */ }
// Attach handlers for system-like messages rendered from JSON (view/download buttons + highlight)
try {
const raw = (typeof msg.content === 'string') ? msg.content.trim() : '';