From bb311bd0431bf544acfdb3f8d67b82cd805258f2 Mon Sep 17 00:00:00 2001
From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com>
Date: Mon, 26 Jan 2026 00:45:25 -0500
Subject: [PATCH] Update conversations.php
---
conversations.php | 149 ++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 144 insertions(+), 5 deletions(-)
diff --git a/conversations.php b/conversations.php
index b230b84..6a461da 100644
--- a/conversations.php
+++ b/conversations.php
@@ -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; }
+
+
+
+
@@ -830,6 +860,8 @@
+
+
@@ -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() : '';