up
This commit is contained in:
+105
-63
@@ -704,6 +704,22 @@ if (!isset($_SESSION['user_id'])) {
|
||||
.quick-replies-panel { left: 8px; right: 8px; bottom: 56px; min-width: auto; }
|
||||
}
|
||||
|
||||
/* Indicador de nuevos mensajes */
|
||||
#new-messages-indicator {
|
||||
animation: bounceIn 0.4s ease, pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes bounceIn {
|
||||
0% { transform: translateX(-50%) translateY(20px); opacity: 0; }
|
||||
60% { transform: translateX(-50%) translateY(-10px); opacity: 1; }
|
||||
100% { transform: translateX(-50%) translateY(-6px); opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { box-shadow: 0 6px 20px rgba(37, 211, 102, 0.35); }
|
||||
50% { box-shadow: 0 6px 30px rgba(37, 211, 102, 0.55); }
|
||||
}
|
||||
|
||||
/* Recording controls: rectangular buttons and clearer contrast */
|
||||
#recording-indicator .btn {
|
||||
border-radius: 6px;
|
||||
@@ -1197,44 +1213,25 @@ if (!isset($_SESSION['user_id'])) {
|
||||
|
||||
// Si la conversación del mensaje es la actualmente abierta
|
||||
if (this.currentUserId && String(this.currentUserId) === String(userId)) {
|
||||
console.log('♻️ Mensaje para conversación ACTIVA, procesando...');
|
||||
console.log('♻️ Mensaje para conversación ACTIVA');
|
||||
|
||||
// Verificar si el usuario está al final del chat (dentro de 150px del final)
|
||||
const container = document.getElementById('chat-conversations');
|
||||
const isAtBottom = container ?
|
||||
(container.scrollHeight - container.scrollTop - container.clientHeight) < 150 : true;
|
||||
// SOLUCIÓN: Siempre usar staged messages + indicador
|
||||
// Esto evita el error que borra la pantalla al intentar recargar automáticamente
|
||||
if (!this._stagedMessages) this._stagedMessages = [];
|
||||
if (!this._stagedMessageIds) this._stagedMessageIds = new Set();
|
||||
|
||||
console.log('📍 Posición scroll:', {
|
||||
scrollHeight: container?.scrollHeight,
|
||||
scrollTop: container?.scrollTop,
|
||||
clientHeight: container?.clientHeight,
|
||||
distanceFromBottom: container ? (container.scrollHeight - container.scrollTop - container.clientHeight) : 0,
|
||||
isAtBottom: isAtBottom
|
||||
});
|
||||
// Crear key única para evitar duplicados
|
||||
const msgKey = data.message_id || data.id || `${data.created_at}_${data.content?.substring(0,20)}`;
|
||||
|
||||
if (isAtBottom) {
|
||||
// Usuario está al final: agregar mensaje automáticamente con highlight
|
||||
console.log('✅ Usuario AL FINAL, recargando mensajes...');
|
||||
|
||||
// Marcar que el próximo mensaje nuevo debe tener highlight
|
||||
this._nextMessageUnread = true;
|
||||
|
||||
// Recargar mensajes (esto hará el fetch y renderizará)
|
||||
this.loadMessages(this.currentUserId, true, true).then(() => {
|
||||
// Después de cargar, quitar el highlight después de 3 segundos
|
||||
setTimeout(() => {
|
||||
this._removeUnreadHighlights();
|
||||
}, 3000);
|
||||
});
|
||||
} else {
|
||||
// Usuario está leyendo arriba: mostrar indicador de nuevos mensajes
|
||||
console.log('⬆️ Usuario LEYENDO ARRIBA, mostrando indicador');
|
||||
this.showNewMessagesIndicator(1);
|
||||
|
||||
// Guardar mensaje en staging para agregarlo cuando el usuario baje
|
||||
if (!this._stagedMessages) this._stagedMessages = [];
|
||||
if (!this._stagedMessageIds.has(msgKey)) {
|
||||
this._stagedMessageIds.add(msgKey);
|
||||
this._stagedMessages.push(data);
|
||||
console.log('💾 Mensaje guardado en staging. Total staged:', this._stagedMessages.length);
|
||||
console.log('💾 Mensaje guardado en staging. Total:', this._stagedMessages.length);
|
||||
|
||||
// Mostrar indicador de nuevos mensajes
|
||||
this.showNewMessagesIndicator(this._stagedMessages.length);
|
||||
} else {
|
||||
console.log('⏭️ Mensaje duplicado, omitiendo');
|
||||
}
|
||||
} else {
|
||||
console.log('📋 Mensaje para OTRA conversación, solo actualizando lista');
|
||||
@@ -4063,6 +4060,8 @@ if (!isset($_SESSION['user_id'])) {
|
||||
|
||||
// set as selected file and show preview
|
||||
this.selectedFile = file;
|
||||
// Marcar como grabación de voz para enviar como nota de voz (ptt=true)
|
||||
this._isVoiceRecording = true;
|
||||
this.showMediaPreview(file);
|
||||
// restore mic UI to default (not recording)
|
||||
try {
|
||||
@@ -4184,28 +4183,51 @@ if (!isset($_SESSION['user_id'])) {
|
||||
el.style.transform = 'translateX(-50%)';
|
||||
el.style.bottom = '84px';
|
||||
el.style.zIndex = '1500';
|
||||
el.className = 'btn btn-primary';
|
||||
el.style.padding = '8px 14px';
|
||||
el.style.borderRadius = '20px';
|
||||
el.style.boxShadow = '0 6px 18px rgba(2,6,23,0.12)';
|
||||
el.className = 'btn btn-success';
|
||||
el.style.padding = '10px 18px';
|
||||
el.style.borderRadius = '24px';
|
||||
el.style.boxShadow = '0 6px 20px rgba(37, 211, 102, 0.35)';
|
||||
el.style.cursor = 'pointer';
|
||||
el.style.fontWeight = '600';
|
||||
el.style.fontSize = '14px';
|
||||
el.innerHTML = '<i class="fas fa-arrow-down me-2"></i>Nuevo mensaje';
|
||||
el.onclick = async () => {
|
||||
try {
|
||||
// Hide indicator immediately and suspend auto-refresh to avoid races
|
||||
this.hideNewMessagesIndicator();
|
||||
this._suspendAutoRefresh = true;
|
||||
if (this.currentUserId) {
|
||||
// Trigger a full load and follow to bottom
|
||||
await this.loadMessages(this.currentUserId, true, true);
|
||||
}
|
||||
// Clear staged messages since full load will include them
|
||||
console.log('🔄 Clic en indicador de nuevos mensajes');
|
||||
|
||||
// Mostrar loading en el botón
|
||||
el.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Cargando...';
|
||||
el.style.pointerEvents = 'none';
|
||||
|
||||
// Limpiar staged messages
|
||||
this._stagedMessages = [];
|
||||
if (this._stagedMessageIds) this._stagedMessageIds.clear();
|
||||
this._stagedCount = 0;
|
||||
|
||||
// Suspender auto-refresh para evitar conflictos
|
||||
this._suspendAutoRefresh = true;
|
||||
|
||||
if (this.currentUserId) {
|
||||
// Reset loading flag para permitir la carga
|
||||
this.loadingMessages = false;
|
||||
|
||||
// Cargar mensajes
|
||||
await this.loadMessages(this.currentUserId, true, true);
|
||||
|
||||
// Scroll al fondo
|
||||
this.scrollToBottom();
|
||||
console.log('✅ Mensajes cargados correctamente');
|
||||
}
|
||||
|
||||
// Ocultar indicador
|
||||
this.hideNewMessagesIndicator();
|
||||
|
||||
} catch (e) {
|
||||
console.warn('Indicator click failed to load full conversation', e);
|
||||
console.error('❌ Error al cargar mensajes:', e);
|
||||
// En caso de error, intentar ocultar el indicador y restaurar estado
|
||||
this.hideNewMessagesIndicator();
|
||||
} finally {
|
||||
try { this._suspendAutoRefresh = false; } catch(e) {}
|
||||
this._suspendAutoRefresh = false;
|
||||
}
|
||||
};
|
||||
// subtle fade-in
|
||||
@@ -4215,8 +4237,11 @@ if (!isset($_SESSION['user_id'])) {
|
||||
setTimeout(() => { try { el.style.opacity = '1'; el.style.transform = 'translateX(-50%) translateY(-6px)'; } catch(e) {} }, 20);
|
||||
}
|
||||
const c = (typeof count === 'number') ? count : (this._stagedMessages ? this._stagedMessages.length : 0);
|
||||
el.textContent = (c && c > 1) ? `Nuevos mensajes (${c})` : 'Nuevo mensaje';
|
||||
el.innerHTML = (c && c > 1)
|
||||
? `<i class="fas fa-arrow-down me-2"></i>Nuevos mensajes (${c})`
|
||||
: '<i class="fas fa-arrow-down me-2"></i>Nuevo mensaje';
|
||||
el.style.display = 'inline-block';
|
||||
el.style.pointerEvents = 'auto'; // Restaurar por si estaba deshabilitado
|
||||
} catch (e) { console.warn('showNewMessagesIndicator failed', e); }
|
||||
}
|
||||
|
||||
@@ -4257,28 +4282,37 @@ if (!isset($_SESSION['user_id'])) {
|
||||
// Immediately hide indicator to provide instant feedback
|
||||
this.hideNewMessagesIndicator();
|
||||
|
||||
// Append staged messages to conversation model
|
||||
const toApplyCount = this._stagedMessages.length;
|
||||
console.debug('applyStagedMessages: applying', toApplyCount, 'messages');
|
||||
this._stagedMessages.forEach(m => { this.currentMessages.push(m); });
|
||||
|
||||
// clear staged storage
|
||||
console.log('✅ applyStagedMessages: aplicando', toApplyCount, 'mensajes');
|
||||
|
||||
// ENFOQUE SEGURO: En lugar de renderizar incrementalmente,
|
||||
// simplemente recargar los mensajes desde el servidor.
|
||||
// Esto es más seguro y evita problemas de renderizado.
|
||||
const userId = this.currentUserId;
|
||||
|
||||
// Limpiar staged messages primero
|
||||
this._stagedMessages = [];
|
||||
if (this._stagedMessageIds) this._stagedMessageIds.clear();
|
||||
this._stagedCount = 0;
|
||||
|
||||
// Render newly added messages and follow to bottom
|
||||
this.renderMessagesIncremental(false, 0, true);
|
||||
this.scrollToBottom();
|
||||
|
||||
// Update latest timestamp
|
||||
try { const last = this.currentMessages[this.currentMessages.length - 1]; if (last && last.created_at) this._latestMessage = last.created_at; } catch(e){}
|
||||
|
||||
// Recargar mensajes de forma segura
|
||||
if (userId) {
|
||||
this.loadingMessages = false; // Reset flag para permitir la carga
|
||||
this.loadMessages(userId, true, true).then(() => {
|
||||
console.log('✅ Mensajes recargados después de aplicar staged');
|
||||
this.scrollToBottom();
|
||||
}).catch(err => {
|
||||
console.error('❌ Error recargando mensajes:', err);
|
||||
// Fallback: intentar scroll al fondo de lo que ya hay
|
||||
this.scrollToBottom();
|
||||
});
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.warn('applyStagedMessages failed', e);
|
||||
} finally {
|
||||
// allow indicator to show again for future arrivals after short delay
|
||||
setTimeout(() => { this._applyingStaged = false; }, 250);
|
||||
setTimeout(() => { this._applyingStaged = false; }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4717,6 +4751,9 @@ if (!isset($_SESSION['user_id'])) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
// NO es grabación de voz (es archivo subido)
|
||||
this._isVoiceRecording = false;
|
||||
|
||||
// Validar tamaño
|
||||
const maxSize = this.getMaxFileSize(file.type);
|
||||
if (file.size > maxSize) {
|
||||
@@ -4849,7 +4886,10 @@ if (!isset($_SESSION['user_id'])) {
|
||||
media_url: uploadResult.data.url,
|
||||
media_type: uploadResult.data.type,
|
||||
caption: caption || null,
|
||||
filename: this.selectedFile.name
|
||||
filename: this.selectedFile.name,
|
||||
// Para audio: is_voice = true si fue grabado desde micrófono (nota de voz)
|
||||
// is_voice = false si fue subido como archivo (audio normal)
|
||||
is_voice: (uploadResult.data.type === 'audio' && this._isVoiceRecording) ? true : false
|
||||
})
|
||||
});
|
||||
|
||||
@@ -4930,6 +4970,8 @@ if (!isset($_SESSION['user_id'])) {
|
||||
document.getElementById('media-caption').value = '';
|
||||
document.getElementById('file-input').value = '';
|
||||
this.selectedFile = null;
|
||||
// Limpiar flag de grabación de voz
|
||||
this._isVoiceRecording = false;
|
||||
|
||||
// Restaurar botón enviar
|
||||
const sendBtn = document.getElementById('send-btn');
|
||||
|
||||
Reference in New Issue
Block a user