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 = `
`;
- 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"