up
This commit is contained in:
+116
-75
@@ -826,10 +826,6 @@ if (!isset($_SESSION['user_id'])) {
|
||||
.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">
|
||||
@@ -839,7 +835,7 @@ if (!isset($_SESSION['user_id'])) {
|
||||
<div>
|
||||
<h5 class="mb-0">💬 Conversaciones
|
||||
<span class="badge bg-warning text-dark ms-2" style="font-size: 9px; padding: 2px 6px; animation: pulse 2s infinite;">
|
||||
v1.2.1-<?php echo substr(time(), -4); ?>
|
||||
v1.2.2-<?php echo substr(time(), -4); ?>
|
||||
</span>
|
||||
</h5>
|
||||
</div>
|
||||
@@ -910,8 +906,6 @@ if (!isset($_SESSION['user_id'])) {
|
||||
</div>
|
||||
<!-- Botón actualizar mensajes -->
|
||||
<button class="btn btn-sm btn-outline-light" id="refresh-messages-btn" title="Actualizar mensajes"><i class="fas fa-sync-alt"></i></button>
|
||||
<!-- 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>
|
||||
@@ -1218,23 +1212,35 @@ if (!isset($_SESSION['user_id'])) {
|
||||
if (this.currentUserId && String(this.currentUserId) === String(userId)) {
|
||||
console.log('♻️ Mensaje para conversación ACTIVA');
|
||||
|
||||
// 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();
|
||||
// Verificar si el usuario está cerca del final del chat
|
||||
const container = document.getElementById('chat-conversations');
|
||||
const nearBottom = container ? ((container.scrollHeight - container.scrollTop - container.clientHeight) < 150) : true;
|
||||
|
||||
// Crear key única para evitar duplicados
|
||||
const msgKey = data.message_id || data.id || `${data.created_at}_${data.content?.substring(0,20)}`;
|
||||
|
||||
if (!this._stagedMessageIds.has(msgKey)) {
|
||||
this._stagedMessageIds.add(msgKey);
|
||||
this._stagedMessages.push(data);
|
||||
console.log('💾 Mensaje guardado en staging. Total:', this._stagedMessages.length);
|
||||
|
||||
// Mostrar indicador de nuevos mensajes
|
||||
this.showNewMessagesIndicator(this._stagedMessages.length);
|
||||
if (nearBottom && !this._userScrolling) {
|
||||
// Usuario está en el final: recargar mensajes automáticamente
|
||||
console.log('🔄 Usuario cerca del final, recargando mensajes...');
|
||||
this.loadMessages(this.currentUserId, true, true).catch(err => {
|
||||
console.error('Error recargando mensajes:', err);
|
||||
});
|
||||
} else {
|
||||
console.log('⏭️ Mensaje duplicado, omitiendo');
|
||||
// Usuario está leyendo historial: usar staged messages
|
||||
console.log('📜 Usuario leyendo historial, guardando en staging...');
|
||||
if (!this._stagedMessages) this._stagedMessages = [];
|
||||
if (!this._stagedMessageIds) this._stagedMessageIds = new Set();
|
||||
|
||||
// Crear key única para evitar duplicados
|
||||
const msgKey = data.message_id || data.id || `${data.created_at}_${data.content?.substring(0,20)}`;
|
||||
|
||||
if (!this._stagedMessageIds.has(msgKey)) {
|
||||
this._stagedMessageIds.add(msgKey);
|
||||
this._stagedMessages.push(data);
|
||||
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');
|
||||
@@ -1380,7 +1386,7 @@ if (!isset($_SESSION['user_id'])) {
|
||||
*/
|
||||
updateConversationInList(data) {
|
||||
try {
|
||||
console.log('📝 Actualizando conversación en lista:', data);
|
||||
console.log('📝 updateConversationInList INICIADO:', data);
|
||||
|
||||
// Asegurar que conversations esté inicializado
|
||||
if (!this.conversations || !Array.isArray(this.conversations)) {
|
||||
@@ -1396,6 +1402,8 @@ if (!isset($_SESSION['user_id'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('👤 User ID extraído:', userId);
|
||||
|
||||
// Si no viene el mensaje, recargar la lista completa para obtener datos actualizados
|
||||
if (!data.message && !data.content && !data.text && !data.last_message) {
|
||||
console.log('⚠️ Evento SSE sin contenido de mensaje, recargando lista completa...');
|
||||
@@ -1412,11 +1420,14 @@ if (!isset($_SESSION['user_id'])) {
|
||||
const message = data.message || data.content || data.text || data.last_message || 'Nuevo mensaje';
|
||||
const timestamp = data.timestamp || data.created_at || new Date().toISOString();
|
||||
|
||||
console.log('💬 Mensaje extraído:', message);
|
||||
console.log('⏰ Timestamp:', timestamp);
|
||||
|
||||
// Buscar la conversación en el array
|
||||
const existingIndex = this.conversations.findIndex(c => String(c.user_id) === String(userId));
|
||||
|
||||
if (existingIndex !== -1) {
|
||||
console.log('✅ Conversación encontrada, actualizando...');
|
||||
console.log('✅ Conversación encontrada en índice:', existingIndex);
|
||||
// Actualizar conversación existente
|
||||
const conv = this.conversations[existingIndex];
|
||||
conv.last_message = message;
|
||||
@@ -1425,14 +1436,17 @@ if (!isset($_SESSION['user_id'])) {
|
||||
// Solo incrementar unread_count si no es la conversación activa
|
||||
if (String(this.currentUserId) !== String(userId)) {
|
||||
conv.unread_count = (conv.unread_count || 0) + 1;
|
||||
console.log('📬 unread_count incrementado a:', conv.unread_count);
|
||||
} else {
|
||||
// Si es la conversación activa, mantener unread_count en 0
|
||||
conv.unread_count = 0;
|
||||
console.log('✅ Conversación activa, unread_count = 0');
|
||||
}
|
||||
|
||||
// Mover al inicio de la lista
|
||||
this.conversations.splice(existingIndex, 1);
|
||||
this.conversations.unshift(conv);
|
||||
console.log('⬆️ Conversación movida al inicio');
|
||||
} else {
|
||||
console.log('➕ Conversación no existe, agregando nueva...');
|
||||
// Nueva conversación, agregar al inicio
|
||||
@@ -1444,22 +1458,25 @@ if (!isset($_SESSION['user_id'])) {
|
||||
last_time: timestamp,
|
||||
unread_count: String(this.currentUserId) !== String(userId) ? 1 : 0
|
||||
});
|
||||
console.log('✅ Nueva conversación agregada');
|
||||
}
|
||||
|
||||
// Re-renderizar solo la lista de conversaciones
|
||||
console.log('🔄 Re-renderizando lista de conversaciones...');
|
||||
console.log('📊 Total conversaciones:', this.conversations.length);
|
||||
console.log('🔍 Filtro activo:', this.conversationFilter);
|
||||
console.log('🔍 this.conversations:', this.conversations);
|
||||
|
||||
if (typeof this.renderConversations === 'function') {
|
||||
console.log('✅ Llamando a renderConversations()...');
|
||||
this.renderConversations();
|
||||
console.log('✅ renderConversations() ejecutado correctamente');
|
||||
} else {
|
||||
console.error('❌ renderConversations no es una función!');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error actualizando conversación en lista:', error);
|
||||
console.error('❌ Error updateConversationInList:', error);
|
||||
console.error('Stack trace:', error.stack);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2110,49 +2127,6 @@ if (!isset($_SESSION['user_id'])) {
|
||||
};
|
||||
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) {
|
||||
(async () => {
|
||||
try {
|
||||
// Check if the service-worker script is actually reachable to avoid noisy 404 warnings
|
||||
const resp = await fetch('/service-worker.js', { method: 'GET', cache: 'no-store' });
|
||||
if (resp && resp.ok) {
|
||||
navigator.serviceWorker.register('/service-worker.js')
|
||||
.then(() => console.debug('SW reg (conv)'))
|
||||
.catch(e => console.warn('SW reg failed', e));
|
||||
} else {
|
||||
console.debug('Service worker not registered: script not found (status ' + (resp && resp.status) + ')');
|
||||
// Remove manifest link if it's missing to avoid extra 404s
|
||||
try {
|
||||
const link = document.querySelector('link[rel="manifest"]');
|
||||
if (link && (!resp || resp.status === 404)) link.parentNode && link.parentNode.removeChild(link);
|
||||
} catch(e) {}
|
||||
}
|
||||
} catch (e) {
|
||||
// Network or fetch failed — don't spam console with errors
|
||||
console.debug('Service worker check failed:', e);
|
||||
}
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
const stopRecBtn = document.getElementById('stop-recording');
|
||||
@@ -2524,7 +2498,9 @@ if (!isset($_SESSION['user_id'])) {
|
||||
|
||||
|
||||
renderConversations() {
|
||||
console.log('🎨 Renderizando conversaciones:', this.conversations.length);
|
||||
console.log('🎨 renderConversations INICIADO');
|
||||
console.log('📊 Total conversaciones:', this.conversations?.length || 0);
|
||||
|
||||
const container = document.getElementById('conversation-list');
|
||||
|
||||
if (!container) {
|
||||
@@ -2631,12 +2607,20 @@ if (!isset($_SESSION['user_id'])) {
|
||||
const wasNearTop = prevScrollTop < 60;
|
||||
|
||||
console.log('🔄 Actualizando innerHTML del container...');
|
||||
console.log('📏 Scroll antes - Top:', prevScrollTop, 'Height:', prevScrollHeight);
|
||||
|
||||
container.innerHTML = html;
|
||||
console.log('✅ DOM actualizado con', this.conversations.length, 'conversaciones');
|
||||
|
||||
console.log('✅ DOM actualizado');
|
||||
console.log('📊 Elementos en DOM:', container.children.length);
|
||||
|
||||
|
||||
// Forzar repaint del DOM
|
||||
void container.offsetHeight;
|
||||
|
||||
const newScrollHeight = container.scrollHeight;
|
||||
const scrollDelta = newScrollHeight - prevScrollHeight;
|
||||
|
||||
console.log('📏 Scroll después - Height:', newScrollHeight, 'Delta:', scrollDelta);
|
||||
|
||||
if (wasNearTop) {
|
||||
// keep at top
|
||||
@@ -2645,10 +2629,15 @@ if (!isset($_SESSION['user_id'])) {
|
||||
// preserve visual offset (avoid jumping) whether the user is scrolling or not
|
||||
container.scrollTop = Math.max(0, prevScrollTop + scrollDelta);
|
||||
}
|
||||
|
||||
console.log('✅ renderConversations COMPLETADO');
|
||||
} catch (e) {
|
||||
// fallback to naive replace if anything failed
|
||||
console.warn('renderConversations: scroll preservation failed', e);
|
||||
container.innerHTML = html;
|
||||
// Forzar repaint
|
||||
void container.offsetHeight;
|
||||
console.log('⚠️ renderConversations COMPLETADO con fallback');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4046,9 +4035,40 @@ if (!isset($_SESSION['user_id'])) {
|
||||
|
||||
this.startRecording = async function() {
|
||||
try {
|
||||
// Verificar que el navegador soporta MediaDevices
|
||||
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
||||
throw new Error('Tu navegador no soporta grabación de audio');
|
||||
}
|
||||
|
||||
// Suspend periodic reload while recording
|
||||
this._suspendAutoRefresh = true;
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
|
||||
// Intentar obtener acceso al micrófono con diferentes configuraciones
|
||||
let stream = null;
|
||||
try {
|
||||
// Intento 1: Configuración básica sin especificar dispositivo
|
||||
stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
echoCancellation: true,
|
||||
noiseSuppression: true,
|
||||
autoGainControl: true
|
||||
}
|
||||
});
|
||||
} catch (err1) {
|
||||
console.warn('Intento 1 falló, probando configuración más simple:', err1.message);
|
||||
try {
|
||||
// Intento 2: Configuración minimalista
|
||||
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
} catch (err2) {
|
||||
console.error('Todos los intentos fallaron');
|
||||
throw err2;
|
||||
}
|
||||
}
|
||||
|
||||
if (!stream) {
|
||||
throw new Error('No se pudo obtener acceso al micrófono');
|
||||
}
|
||||
|
||||
this._mediaRecorder = new MediaRecorder(stream);
|
||||
const chunks = [];
|
||||
this._mediaRecorder.ondataavailable = (e) => { if (e.data && e.data.size) chunks.push(e.data); };
|
||||
@@ -4114,8 +4134,29 @@ if (!isset($_SESSION['user_id'])) {
|
||||
update();
|
||||
this._recordingInterval = setInterval(update, 1000);
|
||||
} catch (err) {
|
||||
console.error('startRecording error', err);
|
||||
alert('No se pudo acceder al micrófono: ' + (err.message||err));
|
||||
console.error('startRecording error', err.name, err.message);
|
||||
|
||||
// Mensajes de error más específicos
|
||||
let errorMsg = 'No se pudo acceder al micrófono';
|
||||
|
||||
if (err.name === 'NotFoundError') {
|
||||
errorMsg = 'No se encontró ningún micrófono. Conecta un micrófono e intenta de nuevo.';
|
||||
} else if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
|
||||
errorMsg = 'Permiso denegado. Permite el acceso al micrófono en la configuración del navegador.';
|
||||
} else if (err.name === 'NotReadableError') {
|
||||
errorMsg = 'El micrófono está siendo usado por otra aplicación.';
|
||||
} else if (err.message) {
|
||||
errorMsg += ': ' + err.message;
|
||||
}
|
||||
|
||||
alert(errorMsg);
|
||||
|
||||
// Limpiar estado
|
||||
this._suspendAutoRefresh = false;
|
||||
const ind = document.getElementById('recording-indicator');
|
||||
if (ind) ind.style.display = 'none';
|
||||
const micEl = document.getElementById('mic-btn');
|
||||
if (micEl) micEl.classList.remove('recording', 'recording-cancel');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user