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() : '';