1/** 2 * HTML Cache Manager - Cache Storage version 3 */ 4class HtmlCacheManager { 5 constructor() { 6 this.cacheName = 'melvil_html_cache'; 7 this.metaKey = 'html-meta'; 8 this.expiryMs = 24 * 60 * 60 * 1000; // 24 hours 9 this.backgroundSyncInProgress = false; 10 this.backgroundSyncCompleted = false; 11 this.backgroundSyncStartTime = null; 12 this.backgroundSyncEndTime = null; 13 this.serverCacheVersion = melvil_filter.cache_version || 0; 14 } 15 16 // Open cache 17 async getCache() { 18 return await caches.open(this.cacheName); 19 } 20 21 // Get meta (timestamp) 22 async getMeta() { 23 const cache = await this.getCache(); 24 const response = await cache.match(this.metaKey); 25 if (response) { 26 const meta = await response.json(); 27 return meta; 28 } 29 return null; 30 } 31 32 // Set meta (timestamp) 33 async setMeta(meta) { 34 const cache = await this.getCache(); 35 await cache.put( 36 this.metaKey, 37 new Response(JSON.stringify(meta), { headers: { 'Content-Type': 'application/json' } }) 38 ); 39 } 40 41 // Check if cache is expired 42 async isExpired() { 43 const meta = await this.getMeta(); 44 45 if (!meta || !meta.timestamp) { 46 47 return true; 48 } 49 50 // Check if server cache version is newer than cached version 51 if (this.serverCacheVersion > 0 && meta.cacheVersion !== this.serverCacheVersion) { 52 53 return true; 54 } 55 56 const isTimeExpired = (Date.now() - meta.timestamp) > this.expiryMs; 57 if (isTimeExpired) { 58 } 59 60 return isTimeExpired; 61 } 62 63 // Clear HTML cards cache (keeps product config intact) 64 async clearCache() { 65 const cache = await this.getCache(); 66 const keys = await cache.keys(); 67 for (const request of keys) { 68 const url = request.url.split('/').pop(); 69 if (url && (url.startsWith('product-card-') || url === this.metaKey)) { 70 await cache.delete(request); 71 } 72 } 73 74 // Automaticky naÄti vÅ¡echny karty znovu na pozadà 75 this.loadAllCards(true); 76 } 77 78 // Save HTML card 79 async setCachedHtml(productId, html) { 80 const cache = await this.getCache(); 81 const cacheKey = new Request(`/product-card-${productId}`, { method: 'GET' }); 82 await cache.put( 83 cacheKey, 84 new Response(html, { headers: { 'Content-Type': 'text/html' } }) 85 ); 86 // Update meta timestamp and cache version 87 await this.setMeta({ 88 timestamp: Date.now(), 89 cacheVersion: this.serverCacheVersion 90 }); 91 } 92 93 // Get HTML card 94 async getCachedHtml(productId) { 95 const cache = await this.getCache(); 96 const cacheKey = new Request(`/product-card-${productId}`, { method: 'GET' }); 97 const response = await cache.match(cacheKey); 98 if (response) { 99 return await response.text(); 100 } 101 return null; 102 } 103 104 // Check if card is cached 105 async isCached(productId) { 106 const cache = await this.getCache(); 107 const cacheKey = new Request(`/product-card-${productId}`, { method: 'GET' }); 108 const response = await cache.match(cacheKey); 109 return !!response; 110 } 111 112 // Get all cached product IDs 113 async getCachedProductIds() { 114 const cache = await this.getCache(); 115 const keys = await cache.keys(); 116 return keys 117 .map(req => req.url.split('/').pop()) 118 .filter(key => key && key.startsWith('product-card-')) 119 .map(key => key.replace('product-card-', '')); 120 } 121 122 // Get missing product IDs from config 123 async getMissingProductIds() { 124 // Wait for product config to be ready 125 const cacheManager = window.melvilProductCache; 126 if (!cacheManager) { 127 return []; 128 } 129 const productsData = await cacheManager.getCachedData(); 130 if (!productsData || !productsData.products) { 131 return []; 132 } 133 const allProductIds = Object.keys(productsData.products); 134 const cachedProductIds = await this.getCachedProductIds(); 135 // Find missing product IDs 136 const missingIds = allProductIds.filter(id => !cachedProductIds.includes(id)); 137 138 return missingIds; 139 } 140 141 // Load missing HTML cards from server
142 async loadMissingCards(missingIds = null, isBackgroundSync = false) { 143 // Pokud nenà zadáno, zjisti chybÄjÃcà ID sám 144 if (!missingIds) { 145 missingIds = await this.getMissingProductIds(); 146 } 147 if (missingIds.length === 0) { 148 return { success: true, loaded: 0, total: 0 }; 149 } 150 151 if (isBackgroundSync) { 152 this.backgroundSyncInProgress = true; 153 this.backgroundSyncStartTime = Date.now(); 154 } else { 155 } 156 157 try { 158 const response = await fetch(melvil_filter.rest_url + 'melvil-filter/v1/load-html-cache/', { 159 method: 'POST', 160 headers: { 161 'X-WP-Nonce': melvil_filter.rest_nonce, 162 'Content-Type': 'application/json' 163 }, 164 body: JSON.stringify(missingIds) 165 }); 166 if (!response.ok) { 167 throw new Error(`HTTP error! status: ${response.status}`); 168 } 169 const data = await response.json(); 170 if (data.success && data.html_data) { 171 let cachedCount = 0; 172 for (const productId in data.html_data) { 173 await this.setCachedHtml(productId, data.html_data[productId]); 174 cachedCount++; 175 } 176 177 if (isBackgroundSync) { 178 this.backgroundSyncInProgress = false; 179 this.backgroundSyncCompleted = true; 180 this.backgroundSyncEndTime = Date.now(); 181 } else { 182 } 183 184 return { 185 success: true, 186 loaded: cachedCount, 187 total: missingIds.length, 188 execution_time: data.execution_time_ms 189 }; 190 } else { 191 throw new Error('Invalid response from server'); 192 } 193 } catch (error) { 194 if (isBackgroundSync) { 195 this.backgroundSyncInProgress = false; 196 this.backgroundSyncCompleted = false; 197 } 198 199 return { success: false, error: error.message }; 200 } 201 } 202 203 // Get HTML for specific product (from cache only, background sync handles missing cards) 204 async getProductHtml(productId) { 205 if (await this.isCached(productId)) { 206 const html = await this.getCachedHtml(productId); 207 return html; 208 } 209 // Card not in cache - background sync will handle it 210 return null; 211 } 212 213 // Get HTML for multiple products 214 async getProductsHtml(productIds) { 215 const results = {}; 216 for (const productId of productIds) { 217 results[productId] = await this.getProductHtml(productId); 218 } 219 return results; 220 } 221 222 // Get cache statistics 223 async getCacheStats() { 224 const cachedIds = await this.getCachedProductIds(); 225 let totalSize = 0; 226 for (const productId of cachedIds) { 227 const html = await this.getCachedHtml(productId); 228 totalSize += html ? html.length : 0; 229 } 230 return { 231 cached_count: cachedIds.length, 232 total_size: this.formatBytes(totalSize), 233 average_size: cachedIds.length > 0 ? this.formatBytes(totalSize / cachedIds.length) : '0 Bytes' 234 }; 235 } 236 237 // Check if background sync is in progress 238 isBackgroundSyncInProgress() { 239 return this.backgroundSyncInProgress; 240 } 241 242 // Check if background sync is completed 243 isBackgroundSyncCompleted() { 244 return this.backgroundSyncCompleted; 245 } 246 247 // Get background sync status 248 getBackgroundSyncStatus() { 249 return { 250 inProgress: this.backgroundSyncInProgress, 251 completed: this.backgroundSyncCompleted, 252 startTime: this.backgroundSyncStartTime, 253 endTime: this.backgroundSyncEndTime, 254 duration: this.backgroundSyncEndTime ? this.backgroundSyncEndTime - this.backgroundSyncStartTime : null 255 }; 256 } 257 258 // Bulk load all cards from server (one GET request)
259 async loadAllCards(isBackgroundSync = false) { 260 if (isBackgroundSync) { 261 this.backgroundSyncInProgress = true; 262 this.backgroundSyncStartTime = Date.now(); 263 } 264 265 try { 266 const response = await fetch(melvil_filter.rest_url + 'melvil-filter/v1/load-html-cache/', { 267 method: 'GET', 268 headers: { 269 'X-WP-Nonce': melvil_filter.rest_nonce 270 } 271 }); 272 if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); 273 const data = await response.json(); 274 275 if (data.success && data.html_data) { 276 let loadedCount = 0; 277 for (const productId in data.html_data) { 278 await this.setCachedHtml(productId, data.html_data[productId]); 279 loadedCount++; 280 } 281 282 if (isBackgroundSync) { 283 this.backgroundSyncInProgress = false; 284 this.backgroundSyncCompleted = true; 285 this.backgroundSyncEndTime = Date.now(); 286 } 287 return { success: true, loaded: loadedCount, total: data.total_products }; 288 } 289 throw new Error('Invalid response from server'); 290 } catch (error) { 291 if (isBackgroundSync) { 292 this.backgroundSyncInProgress = false; 293 this.backgroundSyncCompleted = false; 294 } 295 296 return { success: false, error: error.message }; 297 } 298 } 299 300 // Format bytes to human readable format 301 formatBytes(bytes) { 302 if (bytes === 0) return '0 Bytes'; 303 const k = 1024; 304 const sizes = ['Bytes', 'KB', 'MB', 'GB']; 305 const i = Math.floor(Math.log(bytes) / Math.log(k)); 306 return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; 307 } 308} 309 310// Inicializace pÅi naÄtenà stránky 311// Automaticky naÄte kompletnà cache na pozadà 312document.addEventListener('DOMContentLoaded', function() { 313 const initHtmlCacheManager = async () => { 314 const cacheManager = window.melvilProductCache; 315 if (cacheManager) { 316 try { 317 await cacheManager.getReadyPromise(); 318 window.melvilHtmlCacheManager = new HtmlCacheManager(); 319 320 // Zkontroluj platnost cache na pozadà 321 setTimeout(async () => { 322 try { 323 const isExpired = await window.melvilHtmlCacheManager.isExpired(); 324 if (isExpired) { 325 await window.melvilHtmlCacheManager.clearCache(); 326 } 327 } catch (error) { 328 } 329 }, 1000); 330 331 } catch (error) { 332 } 333 } else { 334 setTimeout(initHtmlCacheManager, 500); 335 } 336 }; 337 initHtmlCacheManager(); 338}); 339 340// Globálnà funkce pro debug 341window.getHtmlCacheStats = async function() { 342 if (window.melvilHtmlCacheManager) { 343 return await window.melvilHtmlCacheManager.getCacheStats(); 344 } 345 return null; 346}; 347 348window.clearHtmlCache = async function() { 349 if (window.melvilHtmlCacheManager) { 350 await window.melvilHtmlCacheManager.clearCache(); 351 } 352}; 353 354window.syncHtmlCache = async function() { 355 if (window.melvilHtmlCacheManager) { 356 return await window.melvilHtmlCacheManager.loadMissingCards(); 357 } 358 return Promise.reject('HTML Cache Manager not available'); 359}; 360 361// Globálnà funkce pro kontrolu stavu background sync 362window.getHtmlCacheBackgroundSyncStatus = function() { 363 if (window.melvilHtmlCacheManager) { 364 return window.melvilHtmlCacheManager.getBackgroundSyncStatus(); 365 } 366 return null; 367}; 368 369window.isHtmlCacheBackgroundSyncInProgress = function() { 370 if (window.melvilHtmlCacheManager) { 371 return window.melvilHtmlCacheManager.isBackgroundSyncInProgress(); 372 } 373 return false; 374}; 375 376window.isHtmlCacheBackgroundSyncCompleted = function() { 377 if (window.melvilHtmlCacheManager) {
378 return window.melvilHtmlCacheManager.isBackgroundSyncCompleted(); 379 } 380 return false; 381};
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.