1 2const endpoint = "/api/search/searchall"; 3 4const focusSearch = sessionStorage.getItem('focus-search') === 'true'; 5sessionStorage.removeItem('focus-search'); 6 7if (window.location.hash === '#focus-search' || focusSearch) { 8 $('#search-input').trigger('focus'); 9} 10 11let searchButton = $("#search-modal-button"), 12 searchResults = $("#search-results"), 13 searchInput = $("#search-input"); 14 15 // Listen for input clearing to reset results 16 searchInput.on('input', function () { 17 const val = $(this).val(); 18 if (!val || val.trim().length === 0) { 19 ClearSearch(); 20 } 21 }); 22const searchContainer = $(".search__results"); 23 24// Pagination state 25let _allSearchHits = []; 26let _currentPage = 0; 27const PAGE_SIZE = 10; 28// loader timing removed â show spinner only while request is pending 29 30function SendSearchRequest(e) { 31 const query = searchInput.val(); 32 SearchRequest(query); 33} 34 35 36// The search request will be sent when user press enter 37searchInput.on("keydown", function (e) { 38 if (e.originalEvent.key == "Enter") { 39 SendSearchRequest(); 40 } 41}); 42 43 44// The search request will be sent when user click on the button 45searchButton.on("click", SendSearchRequest); 46 47 48//send the search request 49function SearchRequest(query) { 50 51 //clear existing search results 52 searchResults.children().remove(); 53 // hide results container until we have results to show 54 if (searchContainer.length) { 55 searchContainer.addClass('hidden').attr('aria-hidden', 'true'); 56 } 57 ShowLoader(); 58 59 if (query.length < 1) { 60 RemoveLoader(); 61 if (searchContainer.length) { 62 searchContainer.addClass('hidden').attr('aria-hidden', 'true'); 63 } 64 return; 65 } 66 67 var minimalRelevanceScore = document.getElementById('minimalRelevanceScore').value; 68 69 fetch(`${endpoint}?query=${query}&score=${minimalRelevanceScore}`) 70 .then((response) => { 71 if (!response.ok) { 72 throw new Error(); 73 } 74 return response.json(); 75 }) 76 .then((data) => { 77 // store hits and render first page 78 _allSearchHits = data.hits || []; 79 _currentPage = 0; 80 RemoveLoader(); 81 renderResultsPage(_currentPage); 82 }) 83 .catch(err => { 84 RemoveLoader(); 85 // hide results on error 86 if (searchContainer.length) { 87 searchContainer.addClass('hidden').attr('aria-hidden', 'true'); 88 } 89 searchResults.append("<li>Noe gikk gale med søket. Vennligst prøv igjen senere.</li>"); 90 }); 91} 92 93function ShowLoader() { 94 // Render spinner into the dedicated spinner container so it's easy to find in DOM 95 const spinnerContainer = $("#search-spinnerContainer"); 96 if (spinnerContainer.length) { 97 // avoid re-adding 98 if (spinnerContainer.find('.spinner').length === 0) { 99 spinnerContainer.html('<div class="spinner" aria-hidden="true"></div>'); 100 } 101 } else { 102 // fallback to previous behaviour: append inside results list 103 if (searchResults.find('.search__loader').length === 0) { 104 const loader = $(`<li class="search__result search__loader"><div class="spinner" aria-hidden="true"></div></li>`); 105 searchResults.append(loader); 106 } 107 } 108} 109 110function RemoveLoader() { 111 const spinnerContainer = $("#search-spinnerContainer"); 112 if (spinnerContainer.length) { 113 spinnerContainer.empty(); 114 } 115 searchResults.find('.search__loader').remove(); 116} 117 118 119function renderResultsPage(page) { 120 searchResults.empty(); 121 122 const total = _allSearchHits.length; 123 124 // if no results, display message 125 if (total === 0) { 126 searchContainer.removeClass('hidden').attr('aria-hidden', 'false'); 127 searchResults.append(createSearchResultItem("Ingen søkeresultater")); 128 return; 129 } 130 131 // show container when we have results 132 if (searchContainer.length) { 133 searchContainer.removeClass('hidden').attr('aria-hidden', 'false'); 134 } 135 136 const start = page * PAGE_SIZE; 137 const end = Math.min(start + PAGE_SIZE, total); 138 139 for (let i = start; i < end; i++) { 140 const currentItem = _allSearchHits[i]; 141 const titleToShow = getResultTitle(currentItem); 142 const snippet = currentItem.excerpt || ''; 143 const html = `<a class="links__link" href="${currentItem.url}"> 144 <p class="search__result-title">${titleToShow}</p> 145 </a> 146 <p class="search__result-snippet">${snippet}</p>`; 147 searchResults.append(createSearchResultItem(html)); 148 } 149 150 // Pagination controls 151 const totalPages = Math.ceil(total / PAGE_SIZE); 152 153 const pagination = $('<li class="search__result search__pagination" aria-l
153ive="polite"></li>'); 154 const nav = $(`<nav class="pagination" aria-label="Søk pagination"></nav>`); 155 156 // Pagination info (e.g. "1-10 av 57") 157 const pageInfo = $('<div class="search__pagination-info"></div>'); 158 const displayStart = start + 1; 159 const displayEnd = end; 160 pageInfo.text(`Viser ${displayStart}-${displayEnd} av totalt ${total} resultater`) ; 161 pagination.append(pageInfo); 162 163 // Show Prev only if there are previous pages 164 if (page > 0) { 165 const prevBtn = $(`<button class="button button--secondary button--small mr-2" aria-label="Forrige side">Forrige</button>`); 166 prevBtn.on('click', function () { 167 _currentPage = Math.max(0, _currentPage - 1); 168 renderResultsPage(_currentPage); 169 }); 170 nav.append(prevBtn); 171 } 172 173 // Show Next only if there are more pages 174 if (end < total) { 175 const nextBtn = $(`<button class="button button--secondary button--small ml-2" aria-label="Neste side">Neste</button>`); 176 nextBtn.on('click', function () { 177 _currentPage = Math.min(totalPages - 1, _currentPage + 1); 178 renderResultsPage(_currentPage); 179 }); 180 nav.append(nextBtn); 181 } 182 183 pagination.append(nav); 184 searchResults.append(pagination); 185} 186 187function createSearchResultItem(resultBody) { 188 return `<li class="search__result">${resultBody}</li>`; 189} 190 191// Return the best title available for a result: prefer metaTitle, then title 192function getResultTitle(item) { 193 let raw = ''; 194 if (item.metaTitle && item.metaTitle.trim().length > 0) { 195 raw = item.metaTitle; 196 } else if (item.meta_title && item.meta_title.trim().length > 0) { 197 raw = item.meta_title; 198 } else if (item.title && item.title.trim().length > 0) { 199 raw = item.title; 200 } else { 201 raw = 'Uten tittel'; 202 } 203 return escapeHtml(raw); 204} 205 206function escapeHtml(unsafe) { 207 return unsafe 208 .replace(/&/g, '&') 209 .replace(/</g, '<') 210 .replace(/>/g, '>') 211 .replace(/"/g, '"') 212 .replace(/'/g, '''); 213} 214 215function ClearSearch() { 216 _allSearchHits = []; 217 _currentPage = 0; 218 RemoveLoader(); 219 if (searchContainer.length) { 220 searchContainer.addClass('hidden').attr('aria-hidden', 'true'); 221 } 222 searchResults.empty(); 223}
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.