1(function ($) { 2 "use strict"; 3 4 /** 5 * Algolia Autocomplete Config 6 */ 7 8 // Localized Vars 9 var index_name_prefix = algolia_config.algolia_index_name_prefix; 10 var is_wpml_enabled = algolia_config.is_wpml_enabled; 11 var wp_locale = algolia_config.wp_locale; 12 13 // Apply dynamic prefix to each index name 14 var index_name_menu_item = `${index_name_prefix}posts_menu_item`; 15 var index_name_faq = `${index_name_prefix}posts_faq`; 16 var index_name_page = `${index_name_prefix}posts_page`; 17 var index_name_post = `${index_name_prefix}posts_post`; 18 19 $(window).on("load", function () { 20 let currentSearchQuery = ""; 21 22 const templates = { 23 post: ` 24 <a class="suggestion-link" href="{{ data.permalink }}" title="{{ data.post_title }}"> 25 <div class="suggestion-post-attributes"> 26 <span class="suggestion-post-title">{{{ data._highlightResult.post_title.value }}}</span> 27 {{{ data.snippet_content }}} 28 </div> 29 </a> 30 `, 31 empty: ` 32 <div class="autocomplete-empty"> 33 No results matched your query <span class="empty-query">"{{ data.query }}"</span> 34 </div> 35 `, 36 }; 37 38 $("#tmpl-autocomplete-post-suggestion").html(templates.post); 39 $("#tmpl-autocomplete-empty").html(templates.empty); 40 41 const client = algoliasearch(algolia.application_id, algolia.search_api_key); 42 43 /** ORDER OF SECTIONS */ 44 const desiredOrder = [ 45 index_name_menu_item, 46 index_name_faq, 47 index_name_page, 48 index_name_post, 49 ]; 50 51 /** INDEX â LABEL MAPPING */ 52 const labelMap = { 53 [index_name_menu_item]: "Menu Items", 54 [index_name_faq]: "Frequently Asked Questions", 55 [index_name_page]: "Pages", 56 [index_name_post]: "Articles", 57 }; 58 59 /** TEMPLATE RENDERER */ 60 const suggestionTemplate = wp.template("autocomplete-post-suggestion"); 61 62 /** ----------------------------------------------------------- 63 * PROCESS HIT (highlight, snippets, permalink overrides) 64 * ---------------------------------------------------------- */ 65 function processHit(hit) { 66 if (!hit.escaped) { 67 hit.escaped = true; 68 69 Object.keys(hit._highlightResult || {}).forEach((key) => { 70 const val = hit._highlightResult[key]?.value; 71 if (typeof val === "string") { 72 hit._highlightResult[key].value = _.escape(val) 73 .replace(/__ais-highlight__/g, "<em>") 74 .replace(/__\/ais-highlight__/g, "</em>"); 75 } 76 }); 77 78 Object.keys(hit._snippetResult || {}).forEach((key) => { 79 const val = hit._snippetResult[key]?.value; 80 if (typeof val === "string") { 81 hit._snippetResult[key].value = _.escape(val) 82 .replace(/__ais-highlight__/g, "<em>") 83 .replace(/__\/ais-highlight__/g, "</em>"); 84 } 85 }); 86 87 hit.snippet_content = 88 hit._snippetResult?.content?.value 89 ? `<div class="suggestion-snippet">${hit._snippetResult.content.value}</div>` 90 : ""; 91 } 92 93 if (hit.post_type === "faq") { 94 hit.permalink = `${window.location.origin}/?s=${encodeURIComponent( 95 currentSearchQuery 96 )}&faq=${hit.post_id}`; 97 } 98 99 if (hit.post_type === "menu_item" && hit.menu_item_category) { 100 hit.permalink = hit.menu_item_category; 101 } 102 103 return suggestionTemplate(hit); 104 } 105 106 /** ----------------------------------------------------------- 107 * ONE MULTI-INDEX REQUEST â MULTIPLE SECTION SOURCES 108 * ---------------------------------------------------------- */ 109 function createSectionSources() { 110 return desiredOrder.map((indexName) => { 111 return { 112 indexName, 113 label: labelMap[indexName], 114 source: function (query, callback) { 115 currentSearchQuery = query; 116 117 if (!query || query.length < 2) { 118 callback([]); 119 return; 120 } 121 122 const query_map = desiredOrder.map((idx) => { 123 let index_config = { 124 indexName: idx, 125 query, 126 params: { 127 hitsPerPage: 5, 128 attributesToSnippet: ["content:10"], 129 highlightPreTag: "__ais-highlight__", 130 highlightPostTag: "__/ais-highlight__", 131 }, 132 }; 133 134 // Add local info if WPML is enabled 135 if ( is_wpml_enabled ) { 136 index_config.params.filters = 'wpml.locale:"' + wp_locale + '"'; 137 } 138 139 return index_config; 140 }); 141 142 const searchQueries = query_map; 143 144 client.search(searchQueries).then((res) => { 145 // Find this sectionâs index result 146 const sectionResult = res.results.find( 147 (r) => r.index === indexName 148 ); 149 150 if (!sectionResult || !sectionResult.hits.length) { 151 callback([]); 152 return; 153 } 154 155 // Add __indexName and process each hit 156 const processed = sectionResult.hits.map((hit) => { 157 hit.__indexName = indexName; 158 return hit; 159 }); 160 161 callback(processed); 162 }); 163 }, 164 templates: { 165 header: () => 166 `<div class="autocomplete-section-label">${labelMap[indexName]}</div>`, 167 168 suggestion: (hit) => processHit(hit), 169 170 footer: () => { 171 const q = encodeURIComponent(currentSearchQuery); 172 const baseUrl = window.location.origin; 173 // console.log(labelMap[indexName]) 174 // if (labelMap[indexName] === 'Menu Items') labelMap[indexName] = 'Menu Items'; 175 // if (labelMap[indexName] === 'FAQs') labelMap[indexName] = 'FAQs'; 176 // console.log(labelMap[indexName]) 177 return `<div class="aa-suggestion show-all-link"> 178 <a class="suggestion-link" href="${baseUrl}/?s=${q}&type=${labelMap[ 179 indexName 180 ].toLowerCase()}"> 181 <strong>Show all ${labelMap[indexName]}</strong> 182 </a> 183 </div>`; 184 }, 185 }, 186 }; 187 }); 188 } 189 190 /** BUILD SOURCES */ 191 const sources = createSectionSources(); 192 193 /** ----------------------------------------------------------- 194 * AUTOCOMPLETE INITIALIZATION 195 * ---------------------------------------------------------- */ 196 $(algolia.autocomplete.input_selector).each(function () { 197 const $container = $(this).closest(".search-bar--wrapper--form"); 198 if ($container.data("autocomplete-initialized")) return; 199 $container.data("autocomplete-initialized", true); 200 201 const autocomplete = algoliaAutocomplete( 202 this, 203 { 204 debug: algolia.debug, 205 hint: false, 206 openOnFocus: true, 207 appendTo: $container[0], 208 templates: { empty: wp.template("autocomplete-empty") }, 209 }, 210 sources 211 ).on("autocomplete:selected", (e, suggestion) => { 212 e.preventDefault(); 213 window.location.assign( 214 suggestion.permalink || suggestion.posts_url 215 ); 216 }); 217 218 // Announce result count to screen readers via aria-live region 219 autocomplete.on('autocomplete:updated', function () { 220 const $liveRegion = $container.closest('.search-bar--wrapper').find('[aria-live]'); 221 const query = autocomplete.autocomplete.getVal() || ''; 222 const resultCount = $container.find('.aa-suggestion').not('.show-all-link').length; 223 224 if ( query.length < 2 ) { 225 $liveRegion.text(''); 226 } else if ( resultCount > 0 ) { 227 $liveRegion.text( resultCount + ' suggestions available.' ); 228 } else { 229 $liveRegion.text( $liveRegion.data('message') ); 230 } 231 }); 232 233 const resetSelectors = 234 ".js-dropdown-close, .search-reset, .mobile-tray-header .icon-button--close, .nav-search-dropdown.mobile .search-reset"; 235 236 $(resetSelectors).on("click", function () { 237 autocomplete.autocomplete.setVal(""); 238 autocomplete.autocomplete.close(); 239 240 const wrapper = autocomplete.autocomplete.getWrapper(); 241 if (wrapper) { 242 wrapper.style.display = "none"; 243 wrapper.classList.remove("active"); 244 } 245 }); 246 247 const wrapper = autocomplete.autocomplete.getWrapper(); 248 if (wrapper) { 249 const observer = new MutationObserver(() => { 250 const visible = window.getComputedStyle(wrapper).display !== "none"; 251 wrapper.classList.toggle("active", visible); 252 }); 253 observer.observe(wrapper, { 254 attributes: true, 255 attributeFilter: ["style"], 256 }); 257 } 258 }); 259 260 $(".search-submit").on("mousedown", function (e) { 261 e.preventDefault(); 262 const input = $(this).closest(".search-form").find(".aa-input"); 263 input.closest("form")[0].submit(); 264 }); 265 }); 266})(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.