feat(chat): separadores de fecha estilo WhatsApp
Muestra "Hoy", "Ayer" o la fecha entre grupos de mensajes de días distintos. El timestamp del bubble ahora solo muestra la hora. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
42662c2ade
commit
dc5fd6e272
@@ -152,6 +152,11 @@ html, body { height: 100%; overflow: hidden; font-family: -apple-system, BlinkMa
|
||||
.msg-time {
|
||||
font-size: .68rem; color: #aaa; text-align: right; margin-top: 3px;
|
||||
}
|
||||
.date-sep {
|
||||
align-self: center; background: #e9ecef; color: #6c757d;
|
||||
font-size: .73rem; font-weight: 600; padding: 3px 12px;
|
||||
border-radius: 99px; margin: 10px 0; user-select: none;
|
||||
}
|
||||
.msg-type-badge {
|
||||
font-size: .7rem; color: #888; font-style: italic;
|
||||
}
|
||||
@@ -743,6 +748,8 @@ async function pollMessages() {
|
||||
}
|
||||
|
||||
// ── Render mensajes ───────────────────────────────────────────────────────────
|
||||
let _lastRenderedDay = null;
|
||||
|
||||
function renderMessages(msgs, prepend) {
|
||||
const area = document.getElementById('messagesArea');
|
||||
area.innerHTML = '';
|
||||
@@ -755,7 +762,16 @@ function renderMessages(msgs, prepend) {
|
||||
loadBtn.innerHTML = '<i class="fas fa-chevron-up"></i> Ver mensajes anteriores';
|
||||
area.appendChild(loadBtn);
|
||||
|
||||
msgs.forEach(m => area.appendChild(buildBubble(m)));
|
||||
let lastDay = null;
|
||||
msgs.forEach(m => {
|
||||
const day = m.created_at ? msgDay(m.created_at) : null;
|
||||
if (day && day !== lastDay) {
|
||||
area.appendChild(buildDateSep(dateLabel(m.created_at)));
|
||||
lastDay = day;
|
||||
}
|
||||
area.appendChild(buildBubble(m));
|
||||
});
|
||||
_lastRenderedDay = lastDay;
|
||||
area.scrollTop = area.scrollHeight;
|
||||
}
|
||||
|
||||
@@ -763,15 +779,39 @@ function prependMessages(msgs) {
|
||||
const area = document.getElementById('messagesArea');
|
||||
const oldTop = area.scrollHeight - area.scrollTop;
|
||||
const loadBtn = document.getElementById('loadMoreBtn');
|
||||
msgs.forEach(m => area.insertBefore(buildBubble(m), loadBtn.nextSibling));
|
||||
// Día del primer mensaje que ya estaba en pantalla (para no duplicar separador)
|
||||
const firstBubble = loadBtn.nextSibling;
|
||||
const firstExistingDay = firstBubble?.dataset?.msgId
|
||||
? msgDay(msgs[msgs.length - 1]?.created_at || '') // comparar con el último del lote nuevo
|
||||
: null;
|
||||
let lastDay = null;
|
||||
let insertRef = loadBtn.nextSibling;
|
||||
msgs.forEach(m => {
|
||||
const day = m.created_at ? msgDay(m.created_at) : null;
|
||||
if (day && day !== lastDay) {
|
||||
area.insertBefore(buildDateSep(dateLabel(m.created_at)), insertRef);
|
||||
lastDay = day;
|
||||
}
|
||||
area.insertBefore(buildBubble(m), insertRef);
|
||||
insertRef = area.querySelector(`[data-msg-id="${m.id}"]`)?.nextSibling || insertRef;
|
||||
});
|
||||
// Eliminar separador duplicado si el último día del lote coincide con el primero ya visible
|
||||
const seps = area.querySelectorAll('.date-sep');
|
||||
for (let i = 0; i < seps.length - 1; i++) {
|
||||
if (seps[i].textContent === seps[i+1].textContent) seps[i+1].remove();
|
||||
}
|
||||
if (!state.hasMore) loadBtn.style.display = 'none';
|
||||
area.scrollTop = area.scrollHeight - oldTop;
|
||||
}
|
||||
|
||||
function appendMessage(msg) {
|
||||
const area = document.getElementById('messagesArea');
|
||||
const bubble = buildBubble(msg);
|
||||
area.appendChild(bubble);
|
||||
const area = document.getElementById('messagesArea');
|
||||
const day = msg.created_at ? msgDay(msg.created_at) : null;
|
||||
if (day && day !== _lastRenderedDay) {
|
||||
area.appendChild(buildDateSep(dateLabel(msg.created_at)));
|
||||
_lastRenderedDay = day;
|
||||
}
|
||||
area.appendChild(buildBubble(msg));
|
||||
area.scrollTop = area.scrollHeight;
|
||||
}
|
||||
|
||||
@@ -1415,6 +1455,22 @@ function escHtml(str) {
|
||||
return String(str ?? '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
function nl2br(str) { return str.replace(/\n/g, '<br>'); }
|
||||
function dateLabel(ts) {
|
||||
const d = new Date(ts.replace(' ', 'T'));
|
||||
const now = new Date();
|
||||
const yesterday = new Date(now); yesterday.setDate(now.getDate() - 1);
|
||||
if (d.toDateString() === now.toDateString()) return 'Hoy';
|
||||
if (d.toDateString() === yesterday.toDateString()) return 'Ayer';
|
||||
return d.toLocaleDateString('es-CO', { weekday:'short', day:'2-digit', month:'short' });
|
||||
}
|
||||
function buildDateSep(label) {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'date-sep';
|
||||
el.textContent = label;
|
||||
return el;
|
||||
}
|
||||
function msgDay(ts) { return new Date(ts.replace(' ', 'T')).toDateString(); }
|
||||
|
||||
function formatTime(ts) {
|
||||
const d = new Date(ts.replace(' ', 'T'));
|
||||
const now = new Date();
|
||||
@@ -1425,9 +1481,7 @@ function formatTime(ts) {
|
||||
}
|
||||
function formatTimeFull(ts) {
|
||||
const d = new Date(ts.replace(' ', 'T'));
|
||||
const t = d.toLocaleTimeString('es-CO', { hour: '2-digit', minute: '2-digit' });
|
||||
if (d.toDateString() === new Date().toDateString()) return t;
|
||||
return d.toLocaleDateString('es-CO', { day: '2-digit', month: '2-digit' }) + ' ' + t;
|
||||
return d.toLocaleTimeString('es-CO', { hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user