1(function($) { 2 'use strict'; 3 4 let currentType = 'todos'; 5 let currentSearch = ''; 6 let originalSearch = ''; 7 let wasAutoCorrected = false; 8 let isLoading = false; 9 let initialCountsLoaded = false; // Los conteos ya se cargaron en el HTML inicial 10 11 $(document).ready(function() { 12 initSearchTabs(); 13 initPagination(); 14 15 const urlParams = new URLSearchParams(window.location.search); 16 currentType = urlParams.get('type') || 'todos'; 17 18 const urlSearch = urlParams.get('s') || ''; 19 const dataSearch = $('.search-tab').first().data('search') || ''; 20 21 if (urlSearch && dataSearch && urlSearch !== dataSearch) { 22 originalSearch = urlSearch; 23 currentSearch = dataSearch; 24 wasAutoCorrected = true; 25 } else { 26 originalSearch = urlSearch || dataSearch; 27 currentSearch = originalSearch; 28 } 29 30 // Los conteos iniciales ya vienen calculados del servidor (search.php) 31 initialCountsLoaded = true; 32 }); 33 34 function initSearchTabs() { 35 $('.search-tab').on('click', function(e) { 36 e.preventDefault(); 37 38 if (isLoading) return; 39 40 const type = $(this).data('type'); 41 const search = $(this).data('search'); 42 43 $('.tabs-cat ul li').removeClass('current'); 44 $(this).closest('li').addClass('current'); 45 46 $('html, body').animate({ 47 scrollTop: $('#search-results-container').offset().top - 100 48 }, 300); 49 50 updateURL(search, type, 1); 51 performSearch(search, type, 1); 52 }); 53 } 54 55 function initPagination() { 56 $(document).on('click', '#search-pagination a.page-numbers', function(e) { 57 e.preventDefault(); 58 59 if (isLoading || $(this).hasClass('current')) return; 60 61 const href = $(this).attr('href'); 62 let page = 1; 63 64 const pageMatch = href.match(/\/page\/(\d+)\//); 65 if (pageMatch) { 66 page = parseInt(pageMatch[1]); 67 } else { 68 const urlParams = new URLSearchParams(href.split('?')[1] || ''); 69 if (urlParams.get('paged')) { 70 page = parseInt(urlParams.get('paged')); 71 } 72 } 73 74 $('html, body').animate({ 75 scrollTop: $('#search-results-container').offset().top - 100 76 }, 300); 77 78 updateURL(currentSearch, currentType, page); 79 performSearch(currentSearch, currentType, page); 80 }); 81 } 82 83 function performSearch(search, type, page) { 84 if (isLoading) return; 85 86 isLoading = true; 87 currentType = type; 88 currentSearch = search; 89 90 showLoading(); 91 92 $.ajax({ 93 url: uandesSearch.ajaxUrl, 94 type: 'POST', 95 data: { 96 action: 'uandes_search', 97 nonce: uandesSearch.nonce, 98 search: search, 99 type: type, 100 paged: page, 101 original_search: wasAutoCorrected ? originalSearch : '', 102 was_auto_corrected: wasAutoCorrected ? 1 : 0 103 }, 104 success: function(response) { 105 if (response.success) { 106 if (response.is_auto_corrected && response.effective_query) { 107 originalSearch = response.original_query; 108 currentSearch = response.effective_query; 109 wasAutoCorrected = true; 110 $('.search-tab').attr('data-search', response.effective_query); 111 } 112 updateResults(response); 113 // NO actualizar conteos - ya vienen del servidor inicial 114 // Los conteos son consistentes para toda la sesión de búsqueda 115 } else { 116 showError('Error al realizar la búsqueda.'); 117 } 118 }, 119 error: function() { 120 showError('Error de conexión.'); 121 }, 122 complete: function() { 123 hideLoading(); 124 isLoading = false;
125 } 126 }); 127 } 128 129 function updateResults(response) { 130 const $container = $('#search-results-container'); 131 132 $container.find('.auto-correction-notice').remove(); 133 134 $('.total-results p').html(`Mostrando ${response.post_count} de ${response.found_posts} resultados`); 135 136 const $existingResults = $container.find('.results-list, .results-not-found'); 137 if ($existingResults.length) { 138 $existingResults.first().replaceWith(response.html); 139 $existingResults.slice(1).remove(); 140 } else { 141 $container.prepend(response.html); 142 } 143 144 const $pagination = $('#search-pagination'); 145 if (response.pagination) { 146 const paginationHtml = ` 147 <nav id="search-pagination" class="navigation pagination" aria-label="Posts pagination"> 148 <h2 class="screen-reader-text">Posts pagination</h2> 149 <div class="nav-links">${response.pagination}</div> 150 </nav> 151 `; 152 if ($pagination.length) { 153 $pagination.replaceWith(paginationHtml); 154 } else { 155 $container.append(paginationHtml); 156 } 157 } else { 158 if ($pagination.length) { 159 $pagination.hide(); 160 } 161 } 162 } 163 164 function updateTabCounts(search) { 165 $.ajax({ 166 url: uandesSearch.ajaxUrl, 167 type: 'POST', 168 data: { 169 action: 'uandes_search_counts', 170 nonce: uandesSearch.nonce, 171 search: search 172 }, 173 success: function(response) { 174 if (response.success && response.data) { 175 $.each(response.data, function(type, data) { 176 const $tab = $(`.search-tab[data-type="${type}"]`); 177 const currentText = $tab.text(); 178 const newText = currentText.replace(/\(\d+\)/, `(${data.count})`); 179 $tab.text(newText); 180 }); 181 } 182 } 183 }); 184 } 185 186 function updateURL(search, type, page) { 187 const params = new URLSearchParams(); 188 189 const urlSearch = wasAutoCorrected ? originalSearch : search; 190 191 if (urlSearch) params.set('s', urlSearch); 192 if (type && type !== 'todos') params.set('type', type); 193 if (page && page > 1) params.set('paged', page); 194 195 const newURL = `${window.location.pathname}?${params.toString()}`; 196 window.history.pushState({}, '', newURL); 197 } 198 199 function showLoading() { 200 $('#search-loading').fadeIn(200); 201 $('#search-results-container').css('opacity', '0.5'); 202 } 203 204 function hideLoading() { 205 $('#search-loading').fadeOut(200); 206 $('#search-results-container').css('opacity', '1'); 207 } 208 209 function showError(message) { 210 alert(message); 211 } 212 213 214 let searchTimeout = null; 215 let currentFocusIndex = -1; 216 217 function initSearchSuggestions() { 218 const $searchInput = $('.desktop-hover-search-form input.search-box'); 219 const $searchContainers = $('.desktop-search-hover'); 220 221 if (!$searchInput.length) return; 222 223 // Initialize a dropdown for each container (mobile/desktop) 224 $searchContainers.each(function() { 225 createDropdown($(this)); 226 }); 227 228 $searchInput.on('focus click', function() { 229 const query = $(this).val().trim(); 230 fetchSuggestions(query, $(this)); 231 }); 232 $searchInput.on('input', function() { 233 const $thisInput = $(this); 234 const query = $thisInput.val().trim(); 235 clearTimeout(searchTimeout); 236 searchTimeout = setTimeout(function() { 237 fetchSuggestions(query, $thisInput); 238 }, 300); 239 }); 240 241 $searchInput.on('keydown', function(e) { 242 const $currentDropdown = $(this).closest('.desktop-search-hover').find('.desktop-search-suggestions-dropdown'); 243 if (!$currentDropdown.is(':visible')) return; 244 245 const $items = $currentDropdown.find('.suggestion-item'); 246 const itemCount = $items.length; 247 248 switch(e.keyCode) { 249 case 40: // Down 250 e.preventDefault(); 251 currentFocusIndex = (currentFocusIndex + 1) % itemCount; 252 updateFocus($items, $currentDropdown); 253 break; 254 case 38: // Up 255 e.preventDefault(); 256 currentFocusIndex = currentFocusIndex <= 0 ? itemCount - 1 : currentFocusIndex - 1; 257 updateFocus($items, $currentDropdown); 258 break; 259 case 27: // Esc 260 hideDropdown($currentDropdown); 261 break; 262 case 13: // Enter 263 if (currentFocusIndex >= 0) { 264 e.preventDefault(); 265 const $focused = $items.eq(currentFocusIndex); 266 if ($focused.length) window.location.href = $focused.data('url'); 267 } 268 break; 269 } 270 }); 271 272 $(document).on('click', function(e) { 273 if (!$(e.target).closest('.desktop-search-hover').length) { 274 hideDropdown(); 275 } 276 }); 277 278 $(document).on('mouseenter', '.suggestion-item', function() { 279 $('.suggestion-item').removeClass('focused'); 280 $(this).addClass('focused'); 281 currentFocusIndex = $(this).index(); 282 }); 283 284 $(document).on('click', '.suggestion-item', function(e) { 285 e.preventDefault(); 286 const url = $(this).data('url'); 287 if (url) { 288 window.location.href = url; 289 } 290 }); 291 } 292 293 function createDropdown($container) { 294 const $dropdown = $('<div>', { // Added 'const' 295 class: 'desktop-search-suggestions-dropdown', 296 css: { display: 'none' } 297 }); 298 299 $container.append($dropdown); 300 } 301 302 function fetchSuggestions(query, $input) { // Add $input as parameter 303 if (typeof uandesSearch === 'undefined') return; 304 305 $.ajax({ 306 url: uandesSearch.ajaxUrl, 307 type: 'POST', 308 data: { 309 action: 'uandes_search_suggestions', 310 nonce: uandesSearch.nonce, 311 query: query 312 }, 313 success: function(response) { 314 // Find the specific dropdown for THIS input 315 const $targetDropdown = $input.closest('.desktop-search-hover').find('.desktop-search-suggestions-dropdown'); 316 317 if (response.success && response.suggestions && response.suggestions.length > 0) { 318 renderSuggestions(response, $targetDropdown); // Pass it along 319 showDropdown($targetDropdown); 320 } else { 321 hideDropdown($targetDropdown); 322 } 323 }, 324 error: function() { 325 const $targetDropdown = $input.closest('.desktop-search-hover').find('.desktop-search-suggestions-dropdown'); 326 hideDropdown($targetDropdown); 327 } 328 }); 329 } 330
331 function renderSuggestions(data, $targetDropdown) { 332 let html = ''; 333 if (data.title) html += '<h3 class="suggestions-title">' + data.title + '</h3>'; 334 html += '<div class="suggestions-grid">'; 335 data.suggestions.forEach(function(item) { 336 html += '<a href="' + item.url + '" class="suggestion-item" data-url="' + item.url + '">'; 337 html += '<span>' + item.title + '</span>'; 338 html += '</a>'; 339 }); 340 html += '</div>'; 341 342 $targetDropdown.html(html); // Use passed dropdown 343 currentFocusIndex = -1; 344 } 345 346 function showDropdown($targetDropdown) { 347 if ($targetDropdown) $targetDropdown.fadeIn(200); 348 } 349 350 function hideDropdown($targetDropdown) { 351 // If no specific dropdown passed, hide ALL (useful for the document click listener) 352 const $target = $targetDropdown || $('.desktop-search-suggestions-dropdown'); 353 $target.fadeOut(200); 354 currentFocusIndex = -1; 355 } 356 357 function updateFocus($items) { 358 $items.removeClass('focused'); 359 if (currentFocusIndex >= 0 && currentFocusIndex < $items.length) { 360 const $focused = $items.eq(currentFocusIndex); 361 $focused.addClass('focused'); 362 363 const dropdown = $dropdown[0]; 364 const focusedElement = $focused[0]; 365 if (dropdown && focusedElement) { 366 const dropdownRect = dropdown.getBoundingClientRect(); 367 const focusedRect = focusedElement.getBoundingClientRect(); 368 369 if (focusedRect.bottom > dropdownRect.bottom) { 370 focusedElement.scrollIntoView(false); 371 } else if (focusedRect.top < dropdownRect.top) { 372 focusedElement.scrollIntoView(true); 373 } 374 } 375 } 376 } 377 378 initSearchSuggestions(); 379 380})(jQuery);
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.