export class TimeoutError extends Error { constructor(message) { super(message); this.name = 'TimeoutError'; } } export function _(key, substitutions) { try { return chrome.i18n.getMessage(key, substitutions); } catch (e) { return key; } } export function showNotification(message, type = 'info', duration = 3000) { const notificationContainer = document.getElementById('notification-container'); const template = document.getElementById('notification-template'); if (!notificationContainer || !template) { alert(`${type.toUpperCase()}: ${message}`); return; } const notificationClone = template.querySelector('.notification').cloneNode(true); notificationClone.className = `notification ${type}`; const icon = notificationClone.querySelector('i'); const span = notificationClone.querySelector('span'); span.textContent = message; let iconClass = 'fa-info-circle'; if (type === 'success') iconClass = 'fa-check-circle'; else if (type === 'error') iconClass = 'fa-exclamation-triangle'; else if (type === 'warning') iconClass = 'fa-exclamation-circle'; icon.className = `fas ${iconClass}`; notificationContainer.prepend(notificationClone); setTimeout(() => { notificationClone.classList.add('show'); }, 50); setTimeout(() => { notificationClone.classList.remove('show'); notificationClone.addEventListener('transitionend', () => notificationClone.remove(), { once: true }); }, duration); } export function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func.apply(this, args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } export function getRelativeTime(timestamp) { const now = Date.now(); const diffSeconds = Math.round((now - timestamp) / 1000); const diffMinutes = Math.round(diffSeconds / 60); const diffHours = Math.round(diffMinutes / 60); const diffDays = Math.round(diffHours / 24); if (diffSeconds < 60) return _('relativeTime_justNow'); if (diffMinutes < 60) return _('relativeTime_minutesAgo', String(diffMinutes)); if (diffHours < 24) return _('relativeTime_hoursAgo', String(diffHours)); if (diffDays === 1) return _('relativeTime_yesterday'); if (diffDays < 7) return _('relativeTime_daysAgo', String(diffDays)); return (new Date(timestamp)).toLocaleDateString(_('appLocaleCode'), { day: 'numeric', month: 'short' }); } export function mostrarSpinner() { const spinner = document.getElementById('spinner'); if (spinner) spinner.style.display = 'block'; } export function ocultarSpinner() { const spinner = document.getElementById('spinner'); if (spinner) spinner.style.display = 'none'; } export function logToConsole(message) { const consoleOutput = document.getElementById('consoleOutput'); if (!consoleOutput) return; const logEntry = document.createElement('div'); logEntry.className = 'console-log-entry'; const time = new Date().toLocaleTimeString(_('appLocaleCode'), { hour12: false }); const timeSpan = `[${time}]`; const messageSpan = `${message.replace(//g, ">")}`; if (message.toLowerCase().includes("error")) { logEntry.classList.add('log-error'); } else if (message.toLowerCase().includes("advertencia") || message.toLowerCase().includes("warning") || message.toLowerCase().includes("pendiente")) { logEntry.classList.add('log-warning'); } else if (message.toLowerCase().includes("success") || message.toLowerCase().includes("finalizado") || message.toLowerCase().includes("éxito") || message.toLowerCase().includes("limpiadas")) { logEntry.classList.add('log-success'); } logEntry.innerHTML = `${timeSpan} ${messageSpan}`; consoleOutput.appendChild(logEntry); consoleOutput.scrollTop = consoleOutput.scrollHeight; } export function emitirEventoActualizacion() { const event = new CustomEvent('indexedDBUpdated'); window.dispatchEvent(event); } export async function fetchWithTimeout(url, options, timeout = 7000) { return new Promise((resolve, reject) => { const controller = new AbortController(); const signal = controller.signal; const timer = setTimeout(() => { controller.abort(); reject(new TimeoutError(`Timeout(${timeout}ms)`)); }, timeout); fetch(url, { ...options, signal }) .then(response => { clearTimeout(timer); resolve(response); }) .catch(err => { clearTimeout(timer); if (err.name === 'AbortError') { reject(new TimeoutError(`Timeout(${timeout}ms)`)); } else { reject(err); } }); }); }