/** * Escape HTML special characters to prevent XSS * Use for any user-generated content rendered via innerHTML * @param {string} str - Untrusted string * @returns {string} - Escaped string safe for innerHTML */ export function escapeHTML(str) { if (str === null || str === undefined) return ''; return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } /** * Format price with currency symbol * @param {number} price - Price value * @param {string} currency - Currency code (EUR, USD, CHF, XMR) * @returns {string} - Formatted price string */ export function formatPrice(price, currency = 'EUR') { if (price === null || price === undefined) return '–'; const symbols = { EUR: '€', USD: '$', CHF: 'CHF', XMR: 'ɱ' }; const symbol = symbols[currency] || currency; if (currency === 'XMR') { return `${price.toFixed(4)} ${symbol}`; } return `${symbol} ${price.toFixed(2)}`; } /** * Format relative time (e.g., "vor 2 Stunden") * @param {Date|string} date - Date to format * @param {string} locale - Locale code * @returns {string} - Relative time string */ export function formatRelativeTime(date, locale = 'de') { const now = new Date(); const then = new Date(date); const diffMs = now - then; const diffSec = Math.floor(diffMs / 1000); const diffMin = Math.floor(diffSec / 60); const diffHour = Math.floor(diffMin / 60); const diffDay = Math.floor(diffHour / 24); const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }); if (diffDay > 0) return rtf.format(-diffDay, 'day'); if (diffHour > 0) return rtf.format(-diffHour, 'hour'); if (diffMin > 0) return rtf.format(-diffMin, 'minute'); return rtf.format(-diffSec, 'second'); } /** * Debounce function calls * @param {Function} fn - Function to debounce * @param {number} delay - Delay in ms * @returns {Function} - Debounced function */ export function debounce(fn, delay = 300) { let timeoutId; return (...args) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => fn.apply(this, args), delay); }; } /** * Truncate string with ellipsis * @param {string} str - String to truncate * @param {number} maxLength - Maximum length * @returns {string} - Truncated string */ export function truncate(str, maxLength = 100) { if (!str || str.length <= maxLength) return str; return str.slice(0, maxLength - 1) + '…'; }