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