diff --git a/conversations.php b/conversations.php index 690f1c3..77450dd 100644 --- a/conversations.php +++ b/conversations.php @@ -2416,6 +2416,8 @@ 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) + for (let i = 0; i < this.conversations.length; i++) { const msg = this.conversations[i]; const mid = String(msg.message_id || msg.id || ('local_' + i)); @@ -2431,57 +2433,134 @@ continue; } - // Renderizar imagen si corresponde - let content = ''; - if (msg.media_type === 'image' || msg.message_type === 'image' || msg.type === 'image') { - const imageUrl = msg.media_url || msg.image_url || msg.url || msg.local_thumb || msg.local_file || msg.media_url_external || msg.content; - const caption = msg.caption || msg.message_text || msg.content || ''; - content = `Imagen recibida`; - if (caption) content += `
${caption}
`; - } else if (msg.media_type === 'video' || msg.message_type === 'video' || msg.type === 'video') { - const videoUrl = msg.media_url || msg.url || msg.local_file || msg.media_url_external; - const caption = msg.caption || msg.message_text || ''; - content = ``; - if (caption) content += `
${caption}
`; - } else if (msg.media_type === 'audio' || msg.message_type === 'audio' || msg.type === 'audio') { - const audioUrl = msg.media_url || msg.url || msg.local_file || msg.media_url_external; - content = ``; + if (existingEl) { + // update in place + try { + const rp = existingEl.querySelector('.reply-preview'); + if (msg.reply_to_message_id) { + const previewSrc = (this.conversations.find(m => m.message_id == msg.reply_to_message_id) || {}).content || ('Mensaje ' + msg.reply_to_message_id); + if (rp) rp.textContent = 'En respuesta a: ' + previewSrc.substring(0,140); + else { + const div = document.createElement('div'); div.className = 'reply-preview'; div.textContent = 'En respuesta a: ' + previewSrc.substring(0,140); existingEl.insertBefore(div, existingEl.firstChild); + } + } else if (rp) rp.remove(); + + const body = existingEl.querySelector('.message-content'); + if (body) { + const mediaPresent = (msg.local_thumb || msg.local_file || msg.media_url_external || msg.media_url); + const existingMedia = body.querySelector('audio,video,img'); + let existingSrc = null; + if (existingMedia) existingSrc = existingMedia.getAttribute('src') || existingMedia.getAttribute('data-src'); + const newMediaUrl = mediaPresent ? (msg.local_thumb || msg.local_file || msg.media_url_external || msg.media_url) : null; + + // If media url unchanged, keep existing element (preserve playback) + if (existingMedia && newMediaUrl && existingSrc && String(existingSrc).includes(newMediaUrl)) { + // nothing to change + } else if (existingMedia && newMediaUrl && (!existingSrc || !String(existingSrc).includes(newMediaUrl))) { + // Replace src but preserve playback state for audio/video + try { + const tag = existingMedia.tagName && existingMedia.tagName.toLowerCase(); + if (tag === 'audio' || tag === 'video') { + const currentTime = existingMedia.currentTime || 0; + const wasPaused = existingMedia.paused; + existingMedia.src = newMediaUrl; + existingMedia.addEventListener('loadedmetadata', () => { + try { + if (typeof existingMedia.duration === 'number' && !isNaN(existingMedia.duration)) { + existingMedia.currentTime = Math.min(currentTime, existingMedia.duration || currentTime); + } + } catch (e) { /* ignore */ } + try { if (!wasPaused) existingMedia.play().catch(()=>{}); } catch(e){} + }, { once: true }); + } else { + // image or other: just replace src + try { existingMedia.src = newMediaUrl; } catch(e) { body.innerHTML = window.renderMediaMessage ? window.renderMediaMessage(msg) : window.escapeHtml(msg.content || '[Mensaje]'); } + } + } catch (e) { + console.warn('preserve media update failed', e); + body.innerHTML = window.renderMediaMessage ? window.renderMediaMessage(msg) : window.escapeHtml(msg.content || '[Mensaje]'); + } + } else if (!existingMedia && newMediaUrl) { + // No existing media element, render new media block without touching other parts + try { + body.innerHTML = window.renderMediaMessage ? window.renderMediaMessage(msg) : window.escapeHtml(msg.content || '[Mensaje]'); + // If after inserting media/text the body appears empty, replace with placeholder + if ((body.textContent || '').trim() === '') { + body.innerHTML = '(Mensaje sin texto)'; + console.warn('Post-update: media/text produced empty body, set placeholder for', msg && (msg.id || msg.message_id)); + } + } catch(e) { /* ignore */ } + } else if (existingMedia && !newMediaUrl) { + // Media removed in new message: replace body with text/content + try { body.innerHTML = window.escapeHtml(msg.content || '[Mensaje]'); } catch(e) { /* ignore */ } + } + } + + 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' }) : ''; + const statusEl = existingEl.querySelector('.message-status'); + if (statusEl) statusEl.innerHTML = this.getStatusIcon(msg.status); + + const reactBadge = existingEl.querySelector('.reaction-badge'); + if (msg.reaction_emoji) { + if (reactBadge) reactBadge.textContent = msg.reaction_emoji; else { + const b = document.createElement('div'); b.className = 'reaction-badge'; b.textContent = msg.reaction_emoji; const bubble = existingEl.querySelector('.message-bubble') || existingEl; bubble.insertBefore(b, bubble.querySelector('.message-actions')); + } + } else if (reactBadge) reactBadge.remove(); + + } catch (e) { console.warn('update message failed', e); } + existing.delete(mid); } else { - content = window.formatMessageContent ? window.formatMessageContent(msg.content || '') : window.escapeHtml(msg.content || '[Mensaje vacío]'); - } + // create new element + try { + const div = document.createElement('div'); + div.className = 'message ' + (msg.direction || 'incoming'); + div.dataset.messageId = mid; - let replyHtml = ''; - if (msg.reply_to_message_id) { - const target = this.conversations.find(m => m.message_id == msg.reply_to_message_id || m.id == msg.reply_to_message_id); - const previewText = target ? (target.content || target.message_text || '').substring(0,140) : ('Mensaje ' + msg.reply_to_message_id); - replyHtml = `
En respuesta a: ${window.escapeHtml(previewText)}
`; - } + let replyHtml = ''; + if (msg.reply_to_message_id) { + const target = this.conversations.find(m => m.message_id == msg.reply_to_message_id || m.id == msg.reply_to_message_id); + const previewText = target ? (target.content || target.message_text || '').substring(0,140) : ('Mensaje ' + msg.reply_to_message_id); + replyHtml = `
En respuesta a: ${window.escapeHtml(previewText)}
`; + } + const mediaPresent = (msg.local_thumb || msg.local_file || msg.media_url_external || msg.media_url); + let content = mediaPresent + ? (window.renderMediaMessage ? window.renderMediaMessage(msg) : (window.formatMessageContent ? window.formatMessageContent(msg.content || '[Mensaje]') : window.escapeHtml(msg.content || '[Mensaje]'))) + : (window.formatMessageContent ? window.formatMessageContent(msg.content || '') : window.escapeHtml(msg.content || '')); - const time = msg.created_at ? new Date(msg.created_at).toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' }) : ''; - const statusIcon = this.getStatusIcon(msg.status); - const reactionHtml = msg.reaction_emoji ? `
${msg.reaction_emoji}
` : ''; + // If after formatting the content is empty (spaces or nothing), show a clearer placeholder + try { + const tmp = (content || '').replace(/<[^>]*>/g, '').trim(); // strip tags and check + if (!tmp) { + console.warn('Rendered message content empty, replacing with placeholder', mid, msg.id, msg.created_at); + content = '(Mensaje sin texto)'; + } + } catch (e) { /* ignore */ } - const div = document.createElement('div'); - div.className = 'message ' + (msg.direction || 'incoming'); - div.dataset.messageId = mid; - div.innerHTML = ` -
- ${replyHtml} -
${content}
- ${reactionHtml} -
- - -
-
${time} ${msg.direction === 'outgoing' ? `${statusIcon}` : ''}
-
- `; + const time = msg.created_at ? new Date(msg.created_at).toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' }) : ''; + const statusIcon = this.getStatusIcon(msg.status); + const reactionHtml = msg.reaction_emoji ? `
${msg.reaction_emoji}
` : ''; - // For prepended older messages, collect to insert at top later - if (prependCount && i < prependCount) { - prependEls.push(div); - } else { - container.appendChild(div); + div.innerHTML = ` +
+ ${replyHtml} +
${content}
+ ${reactionHtml} +
+ + +
+
${time} ${msg.direction === 'outgoing' ? `${statusIcon}` : ''}
+
+ `; + + // For prepended older messages, collect to insert at top later + if (prependCount && i < prependCount) { + prependEls.push(div); + } else { + container.appendChild(div); + } + } catch (e) { console.warn('create message failed', e); } } } @@ -2502,6 +2581,8 @@ if (shouldScroll) { this.scrollToBottom(); } + + } // Mostrar / quitar hint "No hay más mensajes anteriores"