1(function () { 2 'use strict'; 3 4 if (!window.snCoreLiveSearch) { 5 return; 6 } 7 8 var config = window.snCoreLiveSearch; 9 10 function debounce(fn, delay) { 11 var timer = null; 12 var debounced = function () { 13 var ctx = this; 14 var args = arguments; 15 window.clearTimeout(timer); 16 timer = window.setTimeout(function () { 17 fn.apply(ctx, args); 18 }, delay); 19 }; 20 debounced.cancel = function () { 21 window.clearTimeout(timer); 22 timer = null; 23 }; 24 return debounced; 25 } 26 27 function createPanel(form) { 28 var inner = form.querySelector('.header-search-inner'); 29 if (!inner) { 30 return null; 31 } 32 33 var panel = document.createElement('div'); 34 panel.className = 'sn-live-search'; 35 panel.hidden = true; 36 panel.innerHTML = '<div class="sn-live-search__content"></div>'; 37 form.appendChild(panel); 38 return panel; 39 } 40 41 function escapeHtml(value) { 42 return String(value) 43 .replace(/&/g, '&') 44 .replace(/</g, '<') 45 .replace(/>/g, '>') 46 .replace(/"/g, '"') 47 .replace(/'/g, '''); 48 } 49 50 function render(panel, data, query) { 51 var content = panel.querySelector('.sn-live-search__content'); 52 if (!content) { 53 return; 54 } 55 56 var categories = Array.isArray(data.categories) ? data.categories : []; 57 var brands = Array.isArray(data.brands) ? data.brands : []; 58 var products = Array.isArray(data.products) ? data.products : []; 59 60 if (!categories.length && !brands.length && !products.length) { 61 content.innerHTML = '<div class="sn-live-search__empty">' + escapeHtml(config.i18n.noResults) + '</div>'; 62 panel.hidden = false; 63 return; 64 } 65 66 var categoriesHtml = ''; 67 if (categories.length) { 68 categoriesHtml += '<section class="sn-live-search__section">'; 69 categoriesHtml += '<h4>' + escapeHtml(config.i18n.categories) + '</h4>'; 70 categoriesHtml += '<ul class="sn-live-search__categories">'; 71 categories.forEach(function (item) { 72 categoriesHtml += '<li><a href="' + escapeHtml(item.url) + '">' + escapeHtml(item.name) + '</a></li>'; 73 }); 74 categoriesHtml += '</ul></section>'; 75 } 76 77 var brandsHtml = ''; 78 if (brands.length) { 79 brandsHtml += '<section class="sn-live-search__section">'; 80 brandsHtml += '<h4>' + escapeHtml(config.i18n.brands || 'Brand') + '</h4>'; 81 brandsHtml += '<ul class="sn-live-search__brands">'; 82 brands.forEach(function (item) { 83 brandsHtml += '<li><a href="' + escapeHtml(item.url) + '">' + escapeHtml(item.name) + '</a></li>'; 84 }); 85 brandsHtml += '</ul></section>'; 86 } 87 88 var productsHtml = ''; 89 if (products.length) { 90 productsHtml += '<section class="sn-live-search__section">'; 91 productsHtml += '<h4>' + escapeHtml(config.i18n.products) + '</h4>'; 92 productsHtml += '<ul class="sn-live-search__products">'; 93 products.forEach(function (item) { 94 productsHtml += '<li>'; 95 productsHtml += '<a class="sn-live-search__product" href="' + escapeHtml(item.url) + '">'; 96 productsHtml += '<span class="sn-live-search__thumb"><img src="' + escapeHtml(item.image || '') + '" alt="' + escapeHtml(item.name) + '" loading="lazy"></span>'; 97 productsHtml += '<span class="sn-live-search__meta">'; 98 productsHtml += '<span class="sn-live-search__name">' + escapeHtml(item.name) + '</span>'; 99 if (item.priceHtml) { 100 // priceHtml è HTML sicuro generato server-side da wc_price() (WooCommerce). 101 // Può contenere <del>, <ins>, <span> â non va escapato con escapeHtml(). 102 productsHtml += '<span class="sn-live-search__price">' + item.priceHtml + '</span>'; 103 } 104 productsHtml += '</span></a></li>'; 105 }); 106 productsHtml += '</ul></section>'; 107 } 108 109 var allResultsUrl = data.allResultsUrl || ('/?s=' + encodeURIComponent(query) + '&post_type=product'); 110 var hint = config.i18n.enterHint ? '<span class="sn-live-search__hint">' + escapeHtml(config.i18n.enterHint) + '</span>' : ''; 111 var footerHtml = '<div class="sn-live-search__footer"><a href="' + escapeHtml(allResultsUrl) + '">' + escapeHtml(config.i18n.viewAll) + '</a>' + hint + '</div>'; 112 113 content.innerHTML = '<div class="sn-live-search__groups">' + categoriesHtml + brandsHtml + productsHtml + '</div>' + footerHtml; 114 panel.hidden = false;
115 } 116 117 function setLoading(panel) { 118 var content = panel.querySelector('.sn-live-search__content'); 119 if (!content) { 120 return; 121 } 122 content.innerHTML = '' + 123 '<div class="sn-live-search__groups">' + 124 '<section class="sn-live-search__section">' + 125 '<h4>' + escapeHtml(config.i18n.products) + '</h4>' + 126 '<div class="sn-live-search__skeleton-list">' + 127 '<div class="sn-live-search__skeleton-row"></div>' + 128 '<div class="sn-live-search__skeleton-row"></div>' + 129 '<div class="sn-live-search__skeleton-row"></div>' + 130 '</div>' + 131 '</section>' + 132 '</div>' + 133 '<div class="sn-live-search__loading">' + escapeHtml(config.i18n.loading) + '</div>'; 134 panel.hidden = false; 135 } 136 137 function setError(panel, onRetry) { 138 var content = panel.querySelector('.sn-live-search__content'); 139 if (!content) { 140 return; 141 } 142 143 content.innerHTML = '' + 144 '<div class="sn-live-search__error">' + 145 '<p>' + escapeHtml(config.i18n.error || 'Errore durante la ricerca.') + '</p>' + 146 '<button type="button" class="sn-live-search__retry">' + escapeHtml(config.i18n.retry || 'Riprova') + '</button>' + 147 '</div>'; 148 panel.hidden = false; 149 150 var retry = content.querySelector('.sn-live-search__retry'); 151 if (retry) { 152 retry.addEventListener('click', function () { 153 if (typeof onRetry === 'function') { 154 onRetry(); 155 } 156 }); 157 } 158 } 159 160 function hidePanel(panel) { 161 panel.hidden = true; 162 } 163 164 function bindForm(form) { 165 var input = form.querySelector('.search-field'); 166 if (!input) { 167 return; 168 } 169 170 var panel = createPanel(form); 171 if (!panel) { 172 return; 173 } 174 175 var requestSeq = 0; 176 var activeXhr = null; 177 var activeIndex = -1; 178 var debounceMs = Math.max(240, Number(config.debounceMs || 260)); 179 var lastQuery = ''; 180 var fetchResults = null; 181 182 function getResultLinks() { 183 return Array.prototype.slice.call(panel.querySelectorAll('.sn-live-search__categories a, .sn-live-search__brands a, .sn-live-search__product')); 184 } 185 186 function clearActive() { 187 getResultLinks().forEach(function (link) { 188 link.classList.remove('is-active'); 189 }); 190 } 191 192 function setActive(index) { 193 var links = getResultLinks(); 194 if (!links.length) { 195 activeIndex = -1; 196 return; 197 } 198 199 if (index < 0) { 200 index = links.length - 1; 201 } 202 if (index >= links.length) { 203 index = 0; 204 } 205 206 activeIndex = index; 207 clearActive(); 208 links[activeIndex].classList.add('is-active'); 209 links[activeIndex].scrollIntoView({ block: 'nearest' }); 210 } 211 212 function dismissPanel(cancelRequest) { 213 if (fetchResults && typeof fetchResults.cancel === 'function') { 214 fetchResults.cancel(); 215 } 216 if (cancelRequest) { 217 requestSeq += 1; 218 if (activeXhr && activeXhr.readyState !== 4) { 219 activeXhr.abort(); 220 } 221 activeXhr = null; 222 } 223 activeIndex = -1; 224 clearActive(); 225 hidePanel(panel); 226 } 227 228 function requestResults(query) { 229 if (query.length < Number(config.minChars || 2)) { 230 dismissPanel(true); 231 return; 232 } 233 234 lastQuery = query; 235 236 requestSeq += 1; 237 var currentRequest = requestSeq; 238 if (activeXhr && activeXhr.readyState !== 4) { 239 activeXhr.abort(); 240 } 241 setLoading(panel); 242 243 var url = config.endpoint + '?q=' + encodeURIComponent(query) + '&limit=' + encodeURIComponent(String(config.maxResults || 8)); 244 var xhr = new XMLHttpRequest(); 245 activeXhr = xhr; 246 xhr.open('GET', url, true); 247 xhr.timeout = Number(config.timeoutMs || 10000); 248 xhr.setRequestHeader('Accept', 'application/json'); 249 xhr.onreadystatechange = function () { 250 if (xhr.readyState !== 4 || currentRequest !== requestSeq) { 251 return; 252 } 253 254 if (xhr.status < 200 || xhr.status >= 300) { 255 setError(panel, function () { 256 requestResults(lastQuery); 257 }); 258 return; 259 } 260 261 try { 262 var data = JSON.parse(xhr.responseText || '{}'); 263 activeIndex = -1; 264 clearActive(); 265 render(panel, data, query); 266 } catch (error) { 267 setError(panel, function () { 268 requestResults(lastQuery); 269 }); 270 } 271 }; 272 xhr.ontimeout = function () { 273 if (currentRequest !== requestSeq) { 274 return; 275 } 276 setError(panel, function () { 277 requestResults(lastQuery); 278 }); 279 }; 280 xhr.onerror = function () { 281 if (currentRequest !== requestSeq) { 282 return; 283 } 284 setError(panel, function () { 285 requestResults(lastQuery); 286 }); 287 }; 288 xhr.send(); 289 } 290 291 fetchResults = debounce(function () { 292 var query = (input.value || '').trim(); 293 requestResults(query); 294 }, debounceMs); 295 296 input.addEventListener('input', fetchResults); 297 298 input.addEventListener('focus', function () { 299 if ((input.value || '').trim().length >= Number(config.minChars || 2)) { 300 fetchResults(); 301 } 302 }); 303 304 document.addEventListener('click', function (event) { 305 if (!form.contains(event.target)) {
306 dismissPanel(true); 307 } 308 }); 309 310 input.addEventListener('keydown', function (event) { 311 var key = event.key || ''; 312 313 if (key === 'Escape' || key === 'Esc' || event.keyCode === 27) { 314 dismissPanel(true); 315 return; 316 } 317 318 if (panel.hidden) { 319 return; 320 } 321 322 if (key === 'ArrowDown' || key === 'Down' || event.keyCode === 40) { 323 event.preventDefault(); 324 setActive(activeIndex + 1); 325 return; 326 } 327 328 if (key === 'ArrowUp' || key === 'Up' || event.keyCode === 38) { 329 event.preventDefault(); 330 setActive(activeIndex - 1); 331 return; 332 } 333 334 if (key === 'Enter' || event.keyCode === 13) { 335 var links = getResultLinks(); 336 if (activeIndex >= 0 && links[activeIndex]) { 337 event.preventDefault(); 338 window.location.href = links[activeIndex].getAttribute('href'); 339 } 340 } 341 }); 342 } 343 344 document.addEventListener('DOMContentLoaded', function () { 345 document.querySelectorAll('form.header-search').forEach(bindForm); 346 }); 347})();
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.