|
| 1 | +const root_href = document.head.querySelector("link[rel='root']").getAttribute("href") |
| 2 | + |
| 3 | +const minby = (arr, fn) => arr.reduce((a, b) => (fn(a) < fn(b) ? a : b)) |
| 4 | +const maxby = (arr, fn) => arr.reduce((a, b) => (fn(a) > fn(b) ? a : b)) |
| 5 | +const range = (length) => [...Array(length).keys()] |
| 6 | + |
| 7 | +const sortby = (arr, fn) => arr.sort((a, b) => fn(a) - fn(b)) |
| 8 | + |
| 9 | +const setup_search_index = async () => { |
| 10 | + const search_data_href = document.head.querySelector("link[rel='pp-search-data']").getAttribute("href") |
| 11 | + console.log(search_data_href) |
| 12 | + |
| 13 | + const search_data = await (await fetch(search_data_href)).json() |
| 14 | + window.search_data = search_data |
| 15 | + |
| 16 | + console.log(search_data) |
| 17 | + |
| 18 | + // create a search bar powered by lunr |
| 19 | + // const search_bar = document.createElement('div') |
| 20 | + // search_bar.id = 'search-bar' |
| 21 | + // search_bar.innerHTML = ` |
| 22 | + // <input type="text" id="search-input" placeholder="Search..."> |
| 23 | + // <div id="search-results"></div> |
| 24 | + // ` |
| 25 | + // document.body.appendChild(search_bar) |
| 26 | + |
| 27 | + // create a search index |
| 28 | + const before = Date.now() |
| 29 | + const search_index = window.lunr(function () { |
| 30 | + this.ref("url") |
| 31 | + |
| 32 | + this.field("title", { boost: 10 }) |
| 33 | + this.field("tags", { boost: 5 }) |
| 34 | + this.field("text") |
| 35 | + this.metadataWhitelist = ["position"] |
| 36 | + search_data.forEach(function (doc) { |
| 37 | + this.add(doc) |
| 38 | + }, this) |
| 39 | + }) |
| 40 | + const after = Date.now() |
| 41 | + console.info(`lunr: Indexing ${search_data.length} documents took ${after - before}ms`) |
| 42 | + window.search_index = search_index |
| 43 | + |
| 44 | + return { search_data, search_index } |
| 45 | +} |
| 46 | + |
| 47 | +const excerpt_length = 200 |
| 48 | +const excerpt_padding = 50 |
| 49 | + |
| 50 | +const init_search = async () => { |
| 51 | + const query = new URLSearchParams(window.location.search).get("q") |
| 52 | + console.warn({ query }) |
| 53 | + |
| 54 | + document.querySelector(".search-bar.big input").value = query |
| 55 | + |
| 56 | + const { search_data, search_index } = await setup_search_index() |
| 57 | + |
| 58 | + if (query) { |
| 59 | + const results = search_index.search(query) |
| 60 | + console.log(results) |
| 61 | + |
| 62 | + const search_results = document.getElementById("search-results") |
| 63 | + |
| 64 | + if (results.length !== 0) { |
| 65 | + search_results.innerHTML = "" |
| 66 | + results.forEach((result) => { |
| 67 | + const { url, title, tags, text } = search_data.find((doc) => doc.url === result.ref) |
| 68 | + const result_div = document.createElement("a") |
| 69 | + result_div.classList.add("search-result") |
| 70 | + result_div.innerHTML = ` |
| 71 | + <h3 class="title"></h3> |
| 72 | + <p class="snippet"></p> |
| 73 | + <p class="tags"></p> |
| 74 | + ` |
| 75 | + console.log(root_href) |
| 76 | + result_div.querySelector(".title").innerText = title |
| 77 | + result_div.href = new URL(url, new URL(root_href, window.location.href)).href |
| 78 | + result_div.querySelector(".tags").innerText = tags.join(", ") |
| 79 | + result_div.querySelector(".snippet").innerText = text.substring(0, excerpt_length) + "..." |
| 80 | + |
| 81 | + const text_match_positions = Object.values(result?.matchData?.metadata ?? {}) |
| 82 | + .flatMap((z) => z?.text?.position ?? []) |
| 83 | + .sort(([a, _a], [b, _b]) => a - b) |
| 84 | + const title_match_positions = Object.values(result?.matchData?.metadata ?? {}) |
| 85 | + .flatMap((z) => z?.title?.position ?? []) |
| 86 | + .sort(([a, _a], [b, _b]) => a - b) |
| 87 | + |
| 88 | + console.error(title_match_positions) |
| 89 | + if (title_match_positions.length > 0) { |
| 90 | + const strong_el = document.createElement("strong") |
| 91 | + strong_el.innerText = title |
| 92 | + result_div.querySelector(".title").innerHTML = `` |
| 93 | + result_div.querySelector(".title").appendChild(strong_el) |
| 94 | + } |
| 95 | + |
| 96 | + if (text_match_positions.length > 0) { |
| 97 | + // console.log(text_match_positions) |
| 98 | + // console.log(find_longest_run(text_match_positions, 50)) |
| 99 | + // console.log(find_longest_run(text_match_positions, 100)) |
| 100 | + // console.log(find_longest_run(text_match_positions, 200)) |
| 101 | + // console.log(find_longest_run(text_match_positions, 300)) |
| 102 | + // console.log(find_longest_run(text_match_positions, 400)) |
| 103 | + |
| 104 | + const [start_index, num_matches] = find_longest_run(text_match_positions, excerpt_length) |
| 105 | + |
| 106 | + const excerpt_start = text_match_positions[start_index][0] |
| 107 | + const excerpt_end = excerpt_start + excerpt_length |
| 108 | + |
| 109 | + const highlighted_ranges = text_match_positions.slice(start_index, start_index + num_matches) |
| 110 | + |
| 111 | + const elements = highlighted_ranges.flatMap(([h_start, h_length], i) => { |
| 112 | + const h_end = h_start + h_length |
| 113 | + const word = text.slice(h_start, h_end) |
| 114 | + const filler = text.slice(h_end, highlighted_ranges[i + 1]?.[0] ?? excerpt_end) |
| 115 | + const word_el = document.createElement("strong") |
| 116 | + word_el.innerText = word |
| 117 | + return [word_el, filler] |
| 118 | + }) |
| 119 | + |
| 120 | + const snippet_p = result_div.querySelector(".snippet") |
| 121 | + snippet_p.innerHTML = `` |
| 122 | + ;["...", text.slice(excerpt_start - excerpt_padding, excerpt_start).trimStart(), ...elements, "..."].forEach((el) => snippet_p.append(el)) |
| 123 | + } |
| 124 | + |
| 125 | + // text_match_positions.slice(start_index, start_index + num_matches).forEach(([start, length]) => { |
| 126 | + |
| 127 | + search_results.appendChild(result_div) |
| 128 | + }) |
| 129 | + } else { |
| 130 | + search_results.innerText = `No results found for "${query}"` |
| 131 | + } |
| 132 | + } |
| 133 | +} |
| 134 | + |
| 135 | +const count = (arr, fn) => arr.reduce((a, b) => fn(a) + fn(b), 0) |
| 136 | + |
| 137 | +const find_longest_run = (/** @type{Array<[number, number]>} */ positions, max_dist) => { |
| 138 | + const legal_run_size = (start_index) => |
| 139 | + positions.slice(start_index).filter(([start, length]) => start + length < positions[start_index][0] + max_dist).length |
| 140 | + |
| 141 | + console.warn(range(positions.length).map(legal_run_size)) |
| 142 | + |
| 143 | + const best_start = maxby(range(positions.length), legal_run_size) |
| 144 | + const best_length = legal_run_size(best_start) |
| 145 | + return [best_start, best_length] |
| 146 | +} |
| 147 | + |
| 148 | +window.init_search = init_search |
0 commit comments