1/* 2 * BroadbandSwitch.uk - Router Help Hub search 3 * Progressive enhancement: loads the JSON index and filters as the user types. 4 * No external dependencies. Degrades gracefully if fetch fails. 5 * 6 * Cursor: adjust INDEX_URL to the deployed path of data/router-search-index.json. 7 */ 8(function () { 9 "use strict"; 10 var INDEX_URL = "/data/router-search-index.json"; // <-- Cursor: set to deployed path 11 var input = document.getElementById("router-search-input"); 12 var results = document.getElementById("router-search-results"); 13 if (!input || !results) return; 14 15 var data = []; 16 var ready = false; 17 18 fetch(INDEX_URL) 19 .then(function (r) { return r.ok ? r.json() : Promise.reject(r.status); }) 20 .then(function (json) { data = json; ready = true; }) 21 .catch(function () { 22 var hint = document.getElementById("search-hint"); 23 if (hint) hint.textContent = "Search is unavailable right now. Please browse by provider and brand below."; 24 }); 25 26 function score(item, q) { 27 var hay = (item.title + " " + (item.keywords || []).join(" ")).toLowerCase(); 28 if (hay.indexOf(q) === -1) return 0; 29 var s = 0; 30 if (item.title.toLowerCase().indexOf(q) === 0) s += 100; // title starts with query 31 else if (item.title.toLowerCase().indexOf(q) !== -1) s += 60; // title contains 32 (item.keywords || []).forEach(function (k) { 33 if (k.toLowerCase() === q) s += 40; 34 else if (k.toLowerCase().indexOf(q) === 0) s += 20; 35 }); 36 // prefer guide pages slightly over individual models for broad queries 37 if (item.type && item.type.indexOf("guide") !== -1) s += 5; 38 return s; 39 } 40 41 function render(items, q) { 42 results.innerHTML = ""; 43 if (!items.length) { 44 var li = document.createElement("li"); 45 li.className = "search-empty"; 46 li.textContent = 'No match for "' + q + '". Try your ISP, brand or model number.'; 47 results.appendChild(li); 48 return; 49 } 50 items.slice(0, 8).forEach(function (item) { 51 var li = document.createElement("li"); 52 li.className = "search-result"; 53 li.setAttribute("role", "option"); 54 var ip = item.login_ip && item.login_ip.indexOf("Not") === -1 ? item.login_ip : ""; 55 li.innerHTML = 56 '<a href="' + item.url + '">' + 57 '<span class="sr-title">' + item.title + '</span>' + 58 (ip ? '<span class="sr-ip"><code>' + ip.split(" ")[0] + '</code></span>' : '') + 59 '<span class="sr-type">' + (item.type || '') + '</span>' + 60 '</a>'; 61 results.appendChild(li); 62 }); 63 } 64 65 var t; 66 input.addEventListener("input", function () { 67 clearTimeout(t); 68 t = setTimeout(function () { 69 var q = input.value.trim().toLowerCase(); 70 if (!ready || q.length < 2) { results.innerHTML = ""; return; } 71 var scored = data 72 .map(function (item) { return { item: item, s: score(item, q) }; }) 73 .filter(function (x) { return x.s > 0; }) 74 .sort(function (a, b) { return b.s - a.s; }) 75 .map(function (x) { return x.item; }); 76 render(scored, q); 77 }, 120); 78 }); 79 80 // keyboard: Enter jumps to top result 81 input.addEventListener("keydown", function (e) { 82 if (e.key === "Enter") { 83 var first = results.querySelector(".search-result a"); 84 if (first) { window.location.href = first.getAttribute("href"); } 85 } 86 }); 87})();
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.