1import { makeAjaxRequest } from './utils/ajax.js'; 2 3const MIN_SEARCH_LENGTH = 3; 4const SEARCH_DEBOUNCE_DELAY = 300; 5const MAX_CACHE_SIZE = 50; 6 7const initSearch = () => { 8 const $ = window.jQuery; 9 10 const searchFormMobile = document.getElementById('searchform_mobile'); 11 const searchInputMobile = document.getElementById('s'); 12 13 if (searchFormMobile && searchInputMobile) { 14 searchFormMobile.addEventListener('submit', (event) => { 15 const value = searchInputMobile.value.trim(); 16 17 if (!value || value.length < 3) { 18 event.preventDefault(); 19 searchInputMobile.focus(); 20 } 21 }); 22 } 23 24 const searchFormContent = document.getElementById('searchform_content'); 25 26 if (searchFormContent) { 27 const searchInputContent = searchFormContent.querySelector('input[name="s"]'); 28 const searchCloseContent = searchFormContent.querySelector('.header-search__close'); 29 30 if (searchInputContent) { 31 searchFormContent.addEventListener('submit', (event) => { 32 const value = searchInputContent.value.trim(); 33 34 if (!value || value.length < 3) { 35 event.preventDefault(); 36 searchInputContent.focus(); 37 } 38 }); 39 } 40 41 if (searchCloseContent && searchInputContent) { 42 searchCloseContent.addEventListener('click', () => { 43 searchInputContent.value = ''; 44 searchInputContent.focus(); 45 }); 46 } 47 } 48 49 if (!$) { 50 return; 51 } 52 53 const searchCache = new Map(); 54 const inputStates = new WeakMap(); 55 const managedInputs = new Set(); 56 57 const setCacheValue = (term, html) => { 58 if (!term) { 59 return; 60 } 61 62 if (searchCache.has(term)) { 63 searchCache.delete(term); 64 } 65 66 searchCache.set(term, html); 67 68 if (searchCache.size > MAX_CACHE_SIZE) { 69 const firstKey = searchCache.keys().next().value; 70 searchCache.delete(firstKey); 71 } 72 }; 73 74 const getCachedValue = (term) => (searchCache.has(term) ? searchCache.get(term) : undefined); 75 76 const clearPendingTimeout = (input) => { 77 const state = inputStates.get(input); 78 79 if (state && state.timeoutId) { 80 window.clearTimeout(state.timeoutId); 81 state.timeoutId = null; 82 } 83 }; 84 85 const abortActiveRequest = (input) => { 86 const state = inputStates.get(input); 87 88 if (state && state.request && state.request.readyState !== 4) { 89 state.request.abort(); 90 } 91 92 if (state) { 93 state.request = null; 94 } 95 }; 96 97 const resetInputProcessing = (input) => { 98 clearPendingTimeout(input); 99 abortActiveRequest(input); 100 }; 101 102 $('.search_input').each((_, element) => { 103 const input = element; 104 const $input = $(input); 105 const $results = $input.closest('form').find('.search_results'); 106 107 if (!$results.length) { 108 return; 109 } 110 111 inputStates.set(input, { 112 timeoutId: null, 113 request: null, 114 }); 115 managedInputs.add(input); 116 117 $input.on('keyup', () => { 118 const state = inputStates.get(input); 119 const searchValue = $input.val().trim(); 120 121 clearPendingTimeout(input); 122 123 if (searchValue.length < MIN_SEARCH_LENGTH) { 124 abortActiveRequest(input); 125 $results.fadeOut(200).empty(); 126 return; 127 } 128 129 if (searchCache.has(searchValue)) { 130 const cachedHtml = getCachedValue(searchValue); 131 132 if (cachedHtml && cachedHtml.trim()) { 133 $results.fadeIn(200).html(cachedHtml); 134 } else { 135 $results.fadeOut(200).empty(); 136 } 137 return; 138 } 139 140 state.timeoutId = window.setTimeout(() => { 141 state.timeoutId = null; 142 143 abortActiveRequest(input); 144 145 $results.fadeIn(200).html('<div class="search-loading">ÐоиÑк...</div>'); 146 147 state.request = makeAjaxRequest({ 148 data: { 149 action: 'ajax_search', 150 term: searchValue, 151 }, 152 }).done((response) => { 153 state.request = null; 154 155 const hasValidResponse = response && response.success && typeof response.data === 'string'; 156 const responseHtml = hasValidResponse ? response.data : ''; 157 158 setCacheValue(searchValue, responseHtml); 159 160 if (responseHtml.trim()) { 161 $results.html(responseHtml); 162 } else if (hasValidResponse) { 163 $results.fadeOut(200).empty(); 164 } else { 165 $results.html('<div class="search-error">ÐÑибка поиÑка</div>'); 166 } 167 }).fail((xhr, status) => { 168 state.request = null; 169 170 if (status !== 'abort') { 171 $results.html('<div class="search-error">ÐÑибка ÑоединениÑ</div>'); 172 } 173 }); 174 }, SEARCH_DEBOUNCE_DELAY); 175 }); 176 177 $input.on('blur', () => { 178 clearPendingTimeout(input); 179 }); 180 }); 181 182 $(document).on('click', '.header-search__icon', () => { 183 $('.header-search').toggleClass('open'); 184 }); 185 186 $(document).on('click', '.header-search__close', () => { 187 $('.header-search__input').val(''); 188 $('.header-search').removeClass('open'); 189 $('.header-search__items').fadeOut();
190 managedInputs.forEach((input) => { 191 resetInputProcessing(input); 192 }); 193 $('.header-search .search_results').empty(); 194 }); 195 196 $(document).on('input', '.header-search__input', function handleInput() { 197 if ($(this).val().length) { 198 $('.header-search__items').fadeIn(); 199 } else { 200 $('.header-search__items').fadeOut(); 201 } 202 }); 203 204 $(document).on('click', (event) => { 205 const $search = $('.header-search'); 206 207 if (!$search.hasClass('open')) { 208 return; 209 } 210 211 if (!$search.is(event.target) && $search.has(event.target).length === 0) { 212 $('.header-search').removeClass('open'); 213 $('.header-search__input').val(''); 214 $('.header-search__items').fadeOut(); 215 } 216 }); 217}; 218 219export default { 220 name: 'search', 221 init: initSearch, 222};
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.