1(function ($) { 2 $.fn.homelocalExplorerAiSearch = function (rootApp, options, settings) { 3 const debounce = (func, timeout = 300) => { 4 let timer; 5 return (...args) => { 6 clearTimeout(timer); 7 timer = setTimeout(() => { 8 func.apply(this, args); 9 }, timeout); 10 }; 11 }; 12 13 const $container = $(this); 14 15 const selector = { 16 form: ".hfse-ai-form", 17 input: ".ai_search_input", 18 searchId: ".ai_search_id", 19 recentSearchContainer: ".ai_recent_search", 20 recentSearchItem: ".ai_recent_search_item", 21 clearHistoryBtn: ".ai_search_clear_history_btn", 22 loaderModal: "#hfse-ai-search-loader-modal", 23 }; 24 25 let showingRecentSearches = false; 26 let aiRequest; 27 28 const app = { 29 init: () => { 30 // ai search 31 $container.on("submit", selector.form, app.onAiSearchFormSubmit); 32 33 $container.on( 34 "input", 35 selector.input, 36 debounce(app.onAiSearchInputChange, 300), 37 ); 38 39 // hfse-ai-search-abort-btn 40 $(selector.loaderModal).on( 41 "click", 42 ".hfse-ai-search-abort-btn", 43 app.abortyAiSearch, 44 ); 45 46 if (options.hide_ai_search_history !== "1") { 47 app.initSearchHistory(); 48 } 49 50 app.onAiSearchInputChange(); 51 52 $container.on("reset", function () { 53 $container.find(selector.input).val(""); 54 $container.find(selector.searchId).val(""); 55 app.onAiSearchInputChange(); 56 }); 57 }, 58 59 initSearchHistory: () => { 60 $container.on("focus", selector.input, app.showRecentSearches); 61 62 // $container.on("blur", selector.input, app.hideRecentSearches); 63 $(document).on("click", function (event) { 64 if ( 65 showingRecentSearches && 66 !$(event.target).closest($container.find(selector.input)).length && 67 !$(event.target).closest( 68 $container.find(selector.recentSearchContainer), 69 ).length 70 ) { 71 app.hideRecentSearches(); 72 } 73 }); 74 75 $container.on( 76 "click", 77 selector.recentSearchItem, 78 app.onAiSearchLinkClick, 79 ); 80 81 $container.on( 82 "click", 83 selector.clearHistoryBtn, 84 app.clearRecentSearches, 85 ); 86 }, 87 88 clearRecentSearches: (e) => { 89 e.preventDefault(); 90 91 localStorage.removeItem("recentAiSearches"); 92 93 app.hideRecentSearches(); 94 95 return false; 96 }, 97 98 getRecentSearches: () => { 99 let recentSearches = localStorage.getItem("recentAiSearches"); 100 if (recentSearches) { 101 recentSearches = JSON.parse(recentSearches); 102 return recentSearches; 103 } 104 105 return []; 106 }, 107 108 addRecentSearch: (query, query_hash) => { 109 let recentSearches = app.getRecentSearches(); 110 111 // check if query already exists 112 for (let i = 0; i < recentSearches.length; i++) { 113 if (recentSearches[i].query === query) { 114 return; 115 } 116 } 117 118 recentSearches.unshift({ 119 query: query, 120 query_hash: query_hash, 121 }); 122 123 localStorage.setItem( 124 "recentAiSearches", 125 JSON.stringify(recentSearches), 126 ); 127 }, 128 129 showRecentSearches: () => { 130 const $target = $container.find(selector.recentSearchContainer); 131 const recentSearches = app.getRecentSearches(); 132 133 $target.empty(); 134 135 if (recentSearches.length > 0) { 136 const itemsLimit = Math.min( 137 recentSearches.length, 138 parseInt(settings.ai_search_history_limit, 10), 139 ); 140 141 showingRecentSearches = true; 142 143 $target.append(` 144 <div class="ai_recent_search_items"></div> 145 <div class="hk-margin-small hk-flex hk-flex-right"> 146 <button class="uk-button uk-button-link hk-button hk-button-link hk-button-small ai_search_clear_history_btn">${settings.ai_search_clear_history}</button> 147 </div> 148 `); 149 150 const $items = $target.find(".ai_recent_search_items"); 151 152 for (let i = 0; i < itemsLimit; i++) { 153 const $item = $(` 154 <button class="ai_recent_search_item" data-query="${recentSearches[i].query}" data-query-hash="${recentSearches[i].query_hash}">${recentSearches[i].query}</button> 155 `); 156 157 $items.append($item); 158 } 159 160 $target.show(); 161 } 162 }, 163 164 hideRecentSearches: () => { 165 showingRecentSearches = false;
166 $container.find(selector.recentSearchContainer).hide(); 167 }, 168 169 onAiSearchLinkClick: (e) => { 170 e.preventDefault(); 171 172 $container.find(selector.recentSearchContainer).hide(); 173 174 const query = $(e.target).data("query"); 175 const query_hash = $(e.target).data("query-hash"); 176 177 $container.find(selector.input).val(query); 178 $container.find(selector.searchId).val(query_hash); 179 180 app.onAiSearchInputChange(); 181 182 app.onAiSearchFormSubmit(); 183 }, 184 185 onAiSearchInputChange: () => { 186 const $btn = $container.find(".ai_search_submit"); 187 const value = $container.find(selector.input).val(); 188 189 if (value.length > 0) { 190 $btn.prop("disabled", false); 191 } else { 192 $btn.prop("disabled", true); 193 } 194 }, 195 196 abortyAiSearch: () => { 197 if (aiRequest && aiRequest.readyState !== 4) { 198 aiRequest.abort(); 199 // $container.removeClass("loading"); 200 hkUIkit.modal(selector.loaderModal).hide(); 201 } 202 }, 203 204 buildSearchParams: (res, query_hash) => { 205 let params = { 206 ai_search_id: query_hash, 207 pets: res.pets ? "yes" : "no", 208 }; 209 210 if (res.checkin && res.checkout) { 211 params.checkin = res.checkin; 212 params.checkout = res.checkout; 213 } 214 215 // Simple assignments 216 ["bedrooms", "bathrooms", "adults", "children", "title__like"].forEach( 217 (key) => { 218 if (res[key]) params[key] = res[key]; 219 }, 220 ); 221 222 // Array-to-string assignments 223 const arrayFields = { 224 amenities: "amenity", 225 groups: "group", 226 states: "state", 227 cities: "city", 228 types: "type", 229 locations: "location", 230 ctid: "ctid", 231 }; 232 233 for (const [field, paramKey] of Object.entries(arrayFields)) { 234 if (res[field]) params[paramKey] = res[field].join(","); 235 } 236 237 return params; 238 }, 239 240 onAiSearchFormSubmit: () => { 241 // $container.addClass("loading"); 242 243 hkUIkit.modal(selector.loaderModal).show(); 244 245 // $container 246 // .find(".hfse-properties .property-item") 247 // .not(".property-loading") 248 // .remove(); 249 250 // add property loading skeleton. 251 // for (let i = 0; i < options.per_page; i++) { 252 // $container 253 // .find(".hfse-properties") 254 // .append(rootApp.getPropertyLoadingHtml()); 255 // } 256 257 aiRequest = $.ajax({ 258 type: "POST", 259 url: settings.api_url + "homelocal/v1/ai-search/process-query", 260 data: { 261 query: $container.find(selector.input).val(), 262 }, 263 }) 264 .done((response) => { 265 if (response.query_hash) { 266 app.addRecentSearch(response.query, response.query_hash); 267 268 $container.find(selector.searchId).val(response.query_hash); 269 270 if (options.grid === "0") { 271 const params = app.buildSearchParams( 272 response.response, 273 response.query_hash, 274 ); 275 276 const redirectUrl = 277 $container.find(selector.form).attr("action") + 278 "?" + 279 $.param(params); 280 281 window.location.href = redirectUrl; 282 return false; 283 } 284 285 rootApp.setExplorerParams(response.response); 286 rootApp.clearFilterChangeTimer(); 287 rootApp.performFilter(); 288 289 return false; 290 } 291 }) 292 .fail((response) => { 293 if (response.statusText === "abort") { 294 } else if (response.responseJSON && response.responseJSON.message) { 295 alert(response.responseJSON.message); 296 } else { 297 alert(settings.ai_search_failed_message); 298 } 299 }) 300 .always(() => { 301 // $container.removeClass("loading"); 302 hkUIkit.modal(selector.loaderModal).hide(); 303 }); 304 305 return false;
306 }, 307 }; 308 309 return app; 310 }; 311})(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.