1/** 2 * Pomocné funkce pro Ludeon aplikaci 3 */ 4 5/** 6 * Debounce funkce pro optimalizaci vyhledávánà 7 */ 8export function debounce(func, wait) { 9 let timeout; 10 return function executedFunction(...args) { 11 const later = () => { 12 clearTimeout(timeout); 13 func(...args); 14 }; 15 clearTimeout(timeout); 16 timeout = setTimeout(later, wait); 17 }; 18} 19 20/** 21 * Stable poster file key from CSFD URL (e.g. 1661633-resident-evil, 1608318-lanterns). 22 * @param {string} url - CSFD film/episode URL 23 * @param {'entry'|'series'} mode - episodes: 'series' uses parent series segment for shared poster 24 * @returns {string|null} 25 */ 26export function extractCsfdPosterKey(url, mode = 'entry') { 27 if (!url || typeof url !== 'string') { 28 return null; 29 } 30 31 const episodeMatch = url.match(/csfd\.cz\/film\/(\d+)-([^/?#]+)\/(\d+)-([^/?#]+)/i); 32 if (episodeMatch) { 33 const [, seriesId, seriesSlug, epId, epSlug] = episodeMatch; 34 if (mode === 'series') { 35 return `${seriesId}-${seriesSlug}`.toLowerCase(); 36 } 37 return `${epId}-${epSlug}`.toLowerCase(); 38 } 39 40 const filmMatch = url.match(/csfd\.cz\/film\/(\d+)-([^/?#]+)/i); 41 if (filmMatch) { 42 const [, filmId, slug] = filmMatch; 43 return `${filmId}-${slug}`.toLowerCase(); 44 } 45 46 return null; 47} 48 49/** 50 * Sanitizace názvu pro URL (vylepÅ¡ená pro diakritiku) 51 */ 52export function sanitizeTitle(title) { 53 return title 54 .toLowerCase() 55 // Normalizace diakritiky 56 .normalize('NFD') 57 .replace(/[\u0300-\u036f]/g, '') 58 // OdstraÅ speciálnà znaky, ponech ÄÃsla, pÃsmena, mezery a pomlÄky 59 .replace(/[^\w\s-]/g, '') 60 // NahraÄ mezery pomlÄkami 61 .replace(/\s+/g, '-') 62 // OdstraÅ duplicitnà pomlÄky 63 .replace(/-+/g, '-') 64 // OdstraÅ pomlÄky na zaÄátku a konci 65 .replace(/^-+|-+$/g, '') 66 .trim(); 67} 68 69/** 70 * Zobrazà toast zprávu 71 */ 72export function showToast(message, type = 'info', duration = 3000) { 73 // VytvoÅ toast element 74 const toast = document.createElement('div'); 75 toast.className = `toast toast-${type}`; 76 toast.textContent = message; 77 78 // PÅidej styly 79 Object.assign(toast.style, { 80 position: 'fixed', 81 top: '20px', 82 right: '20px', 83 padding: '12px 20px', 84 borderRadius: '8px', 85 color: 'white', 86 fontSize: '14px', 87 fontWeight: '500', 88 zIndex: '10000', 89 opacity: '0', 90 transform: 'translateX(100%)', 91 transition: 'all 0.3s ease', 92 backgroundColor: type === 'error' ? '#ef4444' : 93 type === 'success' ? '#10b981' : '#3b82f6', 94 borderLeft: type === 'success' ? '4px solid #059669' : 95 type === 'error' ? '4px solid #dc2626' : '4px solid #2563eb', 96 boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)' 97 }); 98 99 document.body.appendChild(toast); 100 101 // Animace vstupu 102 requestAnimationFrame(() => { 103 toast.style.opacity = '1'; 104 toast.style.transform = 'translateX(0)'; 105 }); 106 107 // Automatické odstranÄnà 108 setTimeout(() => { 109 toast.style.opacity = '0'; 110 toast.style.transform = 'translateX(100%)'; 111 setTimeout(() => { 112 if (toast.parentNode) { 113 toast.parentNode.removeChild(toast); 114 } 115 }, 300); 116 }, duration); 117} 118 119/** 120 * Logovánà chyb 121 */ 122export function logError(error, context = '') { 123 console.error(`[LudeonApp${context ? ` - ${context}` : ''}]`, error); 124 125 // Zobraz toast pro uživatele 126 showToast('DoÅ¡lo k chybÄ. Zkuste to prosÃm znovu.', 'error'); 127} 128 129/** 130 * Kontrola, zda je element viditelný 131 */ 132export function isElementVisible(element) { 133 const rect = element.getBoundingClientRect(); 134 return ( 135 rect.top >= 0 && 136 rect.left >= 0 && 137 rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && 138 rect.right <= (window.innerWidth || document.documentElement.clientWidth) 139 ); 140} 141 142/** 143 * Throttle funkce pro optimalizaci scroll eventů 144 */ 145export function throttle(func, limit) { 146 let inThrottle; 147 return function() { 148 const args = arguments; 149 const context = this; 150 if (!inThrottle) {
151 func.apply(context, args); 152 inThrottle = true; 153 setTimeout(() => inThrottle = false, limit); 154 } 155 }; 156} 157 158/** 159 * Generovánà náhodného ID 160 */ 161export function generateId() { 162 return Math.random().toString(36).substr(2, 9); 163} 164 165/** 166 * Formátovánà ÄÃsla s mezerami 167 */ 168export function formatNumber(num) { 169 return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' '); 170} 171 172/** 173 * Kontrola, zda je mobilnà zaÅÃzenà 174 */ 175export function isMobile() { 176 return window.innerWidth <= 768; 177} 178 179/** 180 * Kontrola, zda je tablet 181 */ 182export function isTablet() { 183 return window.innerWidth > 768 && window.innerWidth <= 1024; 184} 185 186/** 187 * Kontrola, zda je desktop 188 */ 189export function isDesktop() { 190 return window.innerWidth > 1024; 191}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.