1/** 2 * Find hero: suggestions via /api/geocode (maps.co) with debounce + throttle. 3 */ 4(function () { 5 var DEBOUNCE_MS = 350; 6 var MIN_INTERVAL_MS = 900; 7 8 var form = document.querySelector("form[data-place-autocomplete]"); 9 if (!form) { 10 return; 11 } 12 13 var input = form.querySelector("input[name='mapQuery']"); 14 var clearBtn = form.querySelector(".search-panel-clear"); 15 var list = form.querySelector(".search-suggest"); 16 if (!input || !list) { 17 return; 18 } 19 20 var debounceTimer = null; 21 var throttleTimer = null; 22 var lastFetchStart = 0; 23 var pendingQuery = null; 24 var abortCtl = null; 25 var activeIndex = -1; 26 var results = []; 27 28 function closeList() { 29 list.hidden = true; 30 list.innerHTML = ""; 31 results = []; 32 activeIndex = -1; 33 input.setAttribute("aria-expanded", "false"); 34 } 35 36 function openList() { 37 if (list.children.length) { 38 list.hidden = false; 39 input.setAttribute("aria-expanded", "true"); 40 } 41 } 42 43 var inputRow = form.querySelector(".search-panel-input-row"); 44 if (inputRow && clearBtn && typeof worldmaps3dBindInputClear === "function") { 45 worldmaps3dBindInputClear(inputRow, { 46 input: input, 47 clearBtn: clearBtn, 48 onClear: function () { 49 closeList(); 50 }, 51 }); 52 } 53 54 function goNextFromPickedQuery(queryText) { 55 var q = String(queryText || "").trim(); 56 var flow = form.getAttribute("data-search-flow"); 57 if (flow === "loading-3d") { 58 window.location.href = q 59 ? "find-loading?q=" + encodeURIComponent(q) + "&entry=3d" 60 : "3d-maps"; 61 } else if (flow === "loading-dark") { 62 window.location.href = q 63 ? "find-loading?q=" + encodeURIComponent(q) + "&entry=dark" 64 : "dark-maps"; 65 } else if (flow === "loading-radar") { 66 window.location.href = q 67 ? "find-loading?q=" + encodeURIComponent(q) + "&entry=radar" 68 : "radar-maps"; 69 } else if (flow === "loading-streets") { 70 window.location.href = q 71 ? "find-loading?q=" + encodeURIComponent(q) + "&entry=streets" 72 : "streets-maps"; 73 } else if (flow === "loading-weather") { 74 window.location.href = q 75 ? "find-loading?q=" + encodeURIComponent(q) + "&entry=weather" 76 : "weather-maps"; 77 } else if (flow === "loading") { 78 window.location.href = q 79 ? "find-loading?q=" + encodeURIComponent(q) 80 : "find"; 81 } else if (flow === "pwa-loading" || flow === "pwa-inline") { 82 if (!q) { 83 return; 84 } 85 window.dispatchEvent( 86 new CustomEvent("worldmaps3d-pwa-search", { 87 detail: { q: q }, 88 }) 89 ); 90 } else { 91 window.location.href = q 92 ? "find?q=" + encodeURIComponent(q) 93 : "find"; 94 } 95 } 96 97 function goNextFromPickedItem(item, queryText) { 98 var q = String(queryText || "").trim(); 99 if (!q) { 100 return; 101 } 102 var flow = form.getAttribute("data-search-flow"); 103 /* Inline map search (/maps-search): stay on page, let maps-search.js handle flyTo */ 104 if (flow === "maps-app") { 105 var hitMa = worldmaps3dFirstNominatimResult([item]); 106 if (!hitMa) { 107 return; 108 } 109 if ( 110 typeof worldmaps3dSchedulePersistNominatimItem === "function" && 111 item && 112 !item._fromGeoCache 113 ) { 114 worldmaps3dSchedulePersistNominatimItem(item, q); 115 } 116 window.dispatchEvent( 117 new CustomEvent("worldmaps3d-maps-search-pick", { 118 detail: { hit: hitMa, queryText: q, item: item }, 119 }) 120 ); 121 return; 122 } 123 if (flow === "loading-3d") { 124 var hit3d = worldmaps3dFirstNominatimResult([item]); 125 if (!hit3d) { 126 goNextFromPickedQuery(q); 127 return; 128 } 129 window.location.href = 130 "find-loading?q=" + 131 encodeURIComponent(q) + 132 "&lat=" + 133 encodeURIComponent(String(hit3d.lat)) + 134 "&lon=" + 135 encodeURIComponent(String(hit3d.lon)) + 136 "&entry=3d"; 137 return; 138 } 139 if (flow === "loading-dark") { 140 var hitDark = worldmaps3dFirstNominatimResult([item]); 141 if (!hitDark) { 142 goNextFromPickedQuery(q); 143 return; 144 } 145 window.location.href = 146 "find-loading?q=" + 147 encodeURIComponent(q) + 148 "&lat=" + 149 encodeURIComponent(String(hitDark.lat)) + 150 "&lon=" + 151 encodeURIComponent(String(hitDark.lon)) + 152 "&entry=dark"; 153 return; 154 } 155 if (flow === "loading-radar") { 156 var hitR = worldmaps3dFirstNominatimResult([item]); 157 if (!hitR) { 158 goNextFromPickedQuery(q); 159 return; 160 } 161 window.location.href = 162 "find-loading?q=" + 163 encodeURIComponent(q) + 164 "&lat=" + 165 encodeURIComponent(String(hitR.lat)) + 166 "&lon=" + 167 encodeURIComponent(String(hitR.lon)) + 168 "&entry=radar"; 169 return; 170 } 171 if (flow === "loading-streets") { 172 var hitS = worldmaps3dFirstNominatimResult([item]); 173 if (!hitS) { 174 goNextFromPickedQuery(q); 175 return; 176 } 177 window.location.href = 178 "find-loading?q=" + 179 encodeURIComponent(q) + 180 "&lat=" + 181 encodeURIComponent(String(hitS.lat)) + 182 "&lon=" + 183 encodeURIComponent(String(hitS.lon)) + 184 "&entry=streets"; 185 return; 186 } 187 if (flow === "loading-weather") { 188 var hitW = worldmaps3dFirstNominatimResult([item]); 189 if (!hitW) { 190 goNextFromPickedQuery(q); 191 return; 192 } 193 window.location.href = 194 "find-loading?q=" + 195 encodeURIComponent(q) + 196 "&lat=" + 197 encodeURIComponent(String(hitW.lat)) + 198 "&lon=" + 199 encodeURIComponent(String(hitW.lon)) + 200 "&entry=weather"; 201 return; 202 } 203 if (flow === "pwa-loading" || flow === "pwa-inline") { 204 var hitPwa = worldmaps3dFirstNominatimResult([item]); 205 if (!hitPwa) { 206 goNextFromPickedQuery(q); 207 return; 208 } 209 if ( 210 typeof worldmaps3dSchedulePersistNominatimItem === "function" && 211 item && 212 !item._fromGeoCache 213 ) { 214 worldmaps3dSchedulePersistNominatimItem(item, q); 215 } 216 window.dispatchEvent( 217 new CustomEvent("worldmaps3d-pwa-search", { 218 detail: { 219 q: q, 220 lat: hitPwa.lat, 221 lon: hitPwa.lon, 222 address: String(hitPwa.display_name || "").trim(), 223 }, 224 }) 225 ); 226 return; 227 } 228 if (flow !== "loading") { 229 goNextFromPickedQuery(q); 230 return; 231 } 232 var hit = worldmaps3dFirstNominatimResult([item]); 233 if (!hit) { 234 goNextFromPickedQuery(q); 235 return; 236 } 237 window.location.href = 238 "find-loading" + 239 "?q=" + 240 encodeURIComponent(q) + 241 "&lat=" + 242 encodeURIComponent(String(hit.lat)) + 243 "&lon=" + 244 encodeURIComponent(String(hit.lon)); 245 } 246 247 function render(items) { 248 list.innerHTML = ""; 249 results = items || []; 250 activeIndex = -1; 251 if (!results.length) { 252 closeList(); 253 return; 254 }
255 results.forEach(function (item, idx) { 256 var lines = worldmaps3dNominatimItemToLines(item); 257 var row = document.createElement("button"); 258 row.type = "button"; 259 row.className = "search-suggest-item"; 260 row.setAttribute("role", "option"); 261 row.id = "find-suggest-opt-" + idx; 262 row.dataset.index = String(idx); 263 var t = document.createElement("div"); 264 t.className = "search-suggest-title"; 265 t.textContent = lines.title; 266 row.appendChild(t); 267 if (lines.subtitle) { 268 var s = document.createElement("div"); 269 s.className = "search-suggest-sub"; 270 s.textContent = lines.subtitle; 271 row.appendChild(s); 272 } 273 row.addEventListener("mousedown", function (e) { 274 e.preventDefault(); 275 }); 276 row.addEventListener("click", function () { 277 pickIndex(idx); 278 }); 279 list.appendChild(row); 280 }); 281 list.setAttribute("role", "listbox"); 282 list.removeAttribute("hidden"); 283 openList(); 284 } 285 286 function pickIndex(idx) { 287 if (idx < 0 || idx >= results.length) { 288 return; 289 } 290 var item = results[idx]; 291 var text = String(item.display_name || "").trim(); 292 if (!text) { 293 var lines = worldmaps3dNominatimItemToLines(item); 294 text = lines.title + (lines.subtitle ? ", " + lines.subtitle : ""); 295 } 296 input.value = text; 297 closeList(); 298 goNextFromPickedItem(item, text); 299 } 300 301 function highlight(i) { 302 var nodes = list.querySelectorAll(".search-suggest-item"); 303 nodes.forEach(function (el, j) { 304 el.classList.toggle("is-active", j === i); 305 el.setAttribute("aria-selected", j === i ? "true" : "false"); 306 }); 307 activeIndex = i; 308 } 309 310 function runFetch(q) { 311 if (abortCtl) { 312 abortCtl.abort(); 313 } 314 abortCtl = new AbortController(); 315 var myQuery = q; 316 var searchFn = 317 typeof worldmaps3dSearchPlaceWithCache === "function" 318 ? worldmaps3dSearchPlaceWithCache 319 : worldmaps3dSearchPlace; 320 searchFn(q, { signal: abortCtl.signal }) 321 .then(function (data) { 322 lastFetchStart = Date.now(); 323 var raw = []; 324 if (Array.isArray(data)) { 325 raw = data; 326 } else if (data && Array.isArray(data.results)) { 327 raw = data.results; 328 } 329 330 if (String(input.value).trim() !== myQuery) { 331 return; 332 } 333 render(raw.slice(0, 5)); 334 }) 335 .catch(function (err) { 336 if (err && err.name === "AbortError") { 337 return; 338 } 339 lastFetchStart = Date.now(); 340 closeList(); 341 }); 342 } 343 344 function scheduleFetch(q) { 345 pendingQuery = q; 346 var now = Date.now(); 347 var elapsed = lastFetchStart ? now - lastFetchStart : MIN_INTERVAL_MS; 348 var gap = Math.max(0, MIN_INTERVAL_MS - elapsed); 349 350 clearTimeout(throttleTimer); 351 throttleTimer = setTimeout(function () { 352 runFetch(pendingQuery); 353 }, gap); 354 } 355 356 function onInputValue() { 357 var q = String(input.value || "").trim(); 358 clearTimeout(debounceTimer); 359 if (q.length < 2) { 360 if (abortCtl) { 361 abortCtl.abort(); 362 } 363 closeList(); 364 return; 365 } 366 debounceTimer = setTimeout(function () { 367 scheduleFetch(q); 368 }, DEBOUNCE_MS); 369 } 370 371 input.addEventListener("input", onInputValue); 372 input.addEventListener("focus", function () { 373 if (list.children.length) { 374 openList(); 375 } 376 }); 377 378 input.addEventListener("keydown", function (e) { 379 var k = e.key; 380 if (k === "Escape") { 381 closeList(); 382 return; 383 } 384 if (k === "ArrowDown") { 385 if (list.hidden || !results.length) { 386 return; 387 } 388 e.preventDefault(); 389 var next = activeIndex + 1; 390 if (next >= results.length) { 391 next = 0; 392 } 393 highlight(next); 394 } else if (k === "ArrowUp") { 395 if (list.hidden || !results.length) { 396 return; 397 } 398 e.preventDefault(); 399 var prev = activeIndex - 1; 400 if (prev < 0) { 401 prev = results.length - 1; 402 } 403 highlight(prev); 404 } else if (k === "Enter") { 405 if (!list.hidden && activeIndex >= 0 && results[activeIndex]) { 406 e.preventDefault(); 407 pickIndex(activeIndex); 408 } 409 } 410 }); 411 412 document.addEventListener("click", function (e) { 413 if (form.contains(e.target)) { 414 return; 415 } 416 closeList(); 417 }); 418 419 form.addEventListener("submit", function () { 420 closeList(); 421 }); 422})();
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.