up
This commit is contained in:
+139
-15
@@ -978,9 +978,9 @@
|
||||
const icon = type === 'success' ? '✅' : (type === 'danger' ? '⚠️' : (type === 'warning' ? '⚠' : 'ℹ️'));
|
||||
toast.innerHTML = `<span class="nt-icon">${icon}</span><div style="flex:1; font-size:13px">${message}</div>`;
|
||||
container.appendChild(toast);
|
||||
// Ensure a sensible minimum duration (3s) so very short toasts don't disappear instantly
|
||||
const _minToastDuration = 3000;
|
||||
let _dur = (type === 'success') ? 4000 : 8000;
|
||||
// Ensure a sensible minimum duration (1s) so very short toasts don't disappear instantly
|
||||
const _minToastDuration = 1000;
|
||||
let _dur = (type === 'success') ? 1000 : 1000;
|
||||
_dur = Math.max(_dur, _minToastDuration);
|
||||
setTimeout(() => { if (toast.parentNode) toast.remove(); }, _dur);
|
||||
} catch (e) {
|
||||
@@ -1240,9 +1240,10 @@
|
||||
} catch (e) { console.warn('showNotificationToast -> showSystemNotificationInChat failed', e); }
|
||||
|
||||
// Auto remove (shorter for compact notifications) with minimum enforced
|
||||
const _minToastDuration = 3000;
|
||||
let timeout = notification.duration ? Number(notification.duration) : (notification.cool ? 15000 : 8000);
|
||||
if (!isFinite(timeout) || timeout <= 0) timeout = (notification.cool ? 15000 : 8000);
|
||||
// Reducir mínimo y valores por defecto a 1 segundo (1000 ms)
|
||||
const _minToastDuration = 1000;
|
||||
let timeout = notification.duration ? Number(notification.duration) : (notification.cool ? 1000 : 1000);
|
||||
if (!isFinite(timeout) || timeout <= 0) timeout = (notification.cool ? 1000 : 1000);
|
||||
timeout = Math.max(timeout, _minToastDuration);
|
||||
setTimeout(() => {
|
||||
// on automatic timeout, attempt ack and schedule retry if needed
|
||||
@@ -1721,6 +1722,77 @@
|
||||
this.sendMessage();
|
||||
}
|
||||
});
|
||||
|
||||
// Permitir pegar imágenes desde el portapapeles: convierte el contenido pegado en File y muestra el preview
|
||||
document.getElementById('message-input').addEventListener('paste', (e) => {
|
||||
try {
|
||||
const cd = (e.clipboardData || window.clipboardData);
|
||||
if (!cd) return;
|
||||
|
||||
// Helper: convertir base64 a Blob
|
||||
const b64ToBlob = (b64, mime) => {
|
||||
const bytes = atob(b64);
|
||||
const len = bytes.length;
|
||||
const arr = new Uint8Array(len);
|
||||
for (let i = 0; i < len; i++) arr[i] = bytes.charCodeAt(i);
|
||||
return new Blob([arr], { type: mime });
|
||||
};
|
||||
|
||||
// 1) Si hay archivos directos en clipboard (Chrome/Edge)
|
||||
if (cd.files && cd.files.length) {
|
||||
for (const file of cd.files) {
|
||||
if (file && file.type && file.type.startsWith('image/')) {
|
||||
e.preventDefault && e.preventDefault();
|
||||
const max = this.getMaxFileSize(file.type);
|
||||
if (file.size > max) {
|
||||
alert('El archivo es demasiado grande. Máximo: ' + (max / 1024 / 1024).toFixed(0) + 'MB');
|
||||
return;
|
||||
}
|
||||
this.showMediaPreview(file);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Items (Safari / otros) - buscar imagen o HTML con data URI
|
||||
if (cd.items && cd.items.length) {
|
||||
for (const item of cd.items) {
|
||||
try {
|
||||
if (item.kind === 'file' && item.type && item.type.startsWith('image/')) {
|
||||
e.preventDefault && e.preventDefault();
|
||||
const file = item.getAsFile();
|
||||
if (file) {
|
||||
const max = this.getMaxFileSize(file.type);
|
||||
if (file.size > max) { alert('El archivo es demasiado grande. Máximo: ' + (max / 1024 / 1024).toFixed(0) + 'MB'); return; }
|
||||
this.showMediaPreview(file);
|
||||
return;
|
||||
}
|
||||
} else if (item.kind === 'string' && (item.type === 'text/html' || item.type === 'text/plain')) {
|
||||
// Extraer data URI desde HTML si existe
|
||||
item.getAsString((s) => {
|
||||
const m = s && s.match ? s.match(/src=["']data:(image\/[^;]+);base64,([^"']+)["']/i) : null;
|
||||
if (m) {
|
||||
try {
|
||||
const mime = m[1];
|
||||
const b64 = m[2];
|
||||
const blob = b64ToBlob(b64, mime);
|
||||
const ext = (mime.split('/')[1] || 'png').split('+')[0];
|
||||
const file = new File([blob], 'pasted_image.' + ext, { type: mime });
|
||||
const max = this.getMaxFileSize(file.type);
|
||||
if (file.size > max) { alert('El archivo es demasiado grande. Máximo: ' + (max / 1024 / 1024).toFixed(0) + 'MB'); return; }
|
||||
this.showMediaPreview(file);
|
||||
} catch (err) { console.warn('paste image convert failed', err); }
|
||||
}
|
||||
});
|
||||
// no hacer return inmediato: seguir buscando otros items
|
||||
}
|
||||
} catch (inner) { console.warn('clipboard item parse failed', inner); }
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('paste handler failed', err);
|
||||
}
|
||||
});
|
||||
|
||||
// Adjuntar archivos
|
||||
document.getElementById('attach-btn').addEventListener('click', () => {
|
||||
@@ -3837,21 +3909,28 @@
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const uploadResult = await uploadResponse.json();
|
||||
|
||||
const uploadContentType = uploadResponse.headers.get('content-type') || '';
|
||||
let uploadResult;
|
||||
if (uploadContentType.indexOf('application/json') !== -1) {
|
||||
uploadResult = await uploadResponse.json();
|
||||
} else {
|
||||
const text = await uploadResponse.text();
|
||||
console.error('upload_media returned non-JSON response:\n', text);
|
||||
throw new Error('Respuesta inválida de upload_media.php. Ver consola para más detalles.');
|
||||
}
|
||||
|
||||
if (!uploadResult.success) {
|
||||
throw new Error(uploadResult.error || 'Error subiendo archivo');
|
||||
}
|
||||
|
||||
|
||||
// 2. Enviar mensaje con el archivo
|
||||
const user = this.conversations.find(c => c.user_id === this.currentUserId);
|
||||
const phone = user ? user.phone_number : null;
|
||||
|
||||
|
||||
if (!phone) {
|
||||
throw new Error('No se encontró el número de teléfono');
|
||||
}
|
||||
|
||||
|
||||
const sendResponse = await fetch('api/send_media_message.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -3865,9 +3944,54 @@
|
||||
filename: this.selectedFile.name
|
||||
})
|
||||
});
|
||||
|
||||
const sendResult = await sendResponse.json();
|
||||
|
||||
|
||||
const sendContentType = sendResponse.headers.get('content-type') || '';
|
||||
let sendResult = null;
|
||||
|
||||
if (sendContentType.indexOf('application/json') !== -1) {
|
||||
try {
|
||||
sendResult = await sendResponse.json();
|
||||
} catch (err) {
|
||||
// Content-Type claims JSON but parsing failed: attempt to extract JSON from body
|
||||
const text = await sendResponse.text();
|
||||
console.warn('send_media_message: Content-Type JSON but parse failed. Response body will be inspected for JSON.');
|
||||
console.warn(text);
|
||||
const m = text.match(/(\{[\s\S]*\})/);
|
||||
if (m) {
|
||||
try { sendResult = JSON.parse(m[1]); console.warn('send_media_message: extracted JSON from response.'); } catch (e) { console.warn('send_media_message: extracted JSON parse failed', e); }
|
||||
}
|
||||
if (!sendResult) {
|
||||
// If HTTP status is OK, be forgiving: treat as success but log for investigation
|
||||
if (sendResponse.ok) {
|
||||
console.warn('send_media_message: non-parseable JSON returned but HTTP ok; treating as success.');
|
||||
sendResult = { success: true };
|
||||
} else {
|
||||
throw new Error('Respuesta JSON inválida de send_media_message.php. Ver consola para más detalles.');
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Non-JSON content-type: try to find embedded JSON; if HTTP 200, be forgiving
|
||||
const text = await sendResponse.text();
|
||||
console.warn('send_media_message returned non-JSON response:');
|
||||
console.warn(text.slice(0, 2000));
|
||||
const m = text.match(/(\{[\s\S]*\})/);
|
||||
if (m) {
|
||||
try { sendResult = JSON.parse(m[1]); console.warn('send_media_message: extracted JSON from non-JSON response.'); } catch (e) { console.warn('send_media_message: failed to parse extracted JSON', e); }
|
||||
}
|
||||
|
||||
if (!sendResult) {
|
||||
if (sendResponse.ok) {
|
||||
// Message was likely sent successfully despite odd response — avoid showing false error to user
|
||||
console.warn('send_media_message: non-JSON response but HTTP OK. Treating as success.');
|
||||
sendResult = { success: true };
|
||||
} else {
|
||||
console.error('send_media_message returned non-JSON response and HTTP not OK.');
|
||||
throw new Error('Respuesta inválida de send_media_message.php. Revisa logs del servidor (send_media_message_debug.log y el servidor PHP) para más detalles.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!sendResult.success) {
|
||||
throw new Error(sendResult.error || 'Error enviando mensaje');
|
||||
}
|
||||
|
||||
@@ -1078,6 +1078,8 @@ try {
|
||||
actions.appendChild(btn);
|
||||
toast.appendChild(actions);
|
||||
this.toastsContainer.appendChild(toast);
|
||||
// auto-eliminar toast de sesión expirada después de 1s
|
||||
setTimeout(() => { if (toast.parentNode) toast.remove(); }, 1000);
|
||||
},
|
||||
showToast(notification) {
|
||||
const toast = document.createElement('div');
|
||||
@@ -1164,6 +1166,8 @@ try {
|
||||
}
|
||||
n.close();
|
||||
};
|
||||
// cerrar la notificación nativa automáticamente después de 1s
|
||||
setTimeout(() => { try { n.close(); } catch(e) {} }, 1000);
|
||||
} catch (e) { console.warn('native notification failed', e); }
|
||||
}
|
||||
|
||||
@@ -1182,7 +1186,7 @@ try {
|
||||
setTimeout(() => { o.stop(); ctx.close(); }, 180);
|
||||
} catch (e) { console.warn('Notification sound failed', e); }
|
||||
|
||||
setTimeout(() => { if (toast.parentNode) toast.remove(); }, 18000);
|
||||
setTimeout(() => { if (toast.parentNode) toast.remove(); }, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user