( element: HTMLElement, text: string, query: string, )
| 224 | } |
| 225 | |
| 226 | function appendHighlightedText( |
| 227 | element: HTMLElement, |
| 228 | text: string, |
| 229 | query: string, |
| 230 | ) { |
| 231 | const terms = normalizeSearchText(query).split(/\s+/).filter(Boolean); |
| 232 | if (terms.length === 0) { |
| 233 | element.textContent = text; |
| 234 | return; |
| 235 | } |
| 236 | |
| 237 | const lowerText = text.toLowerCase(); |
| 238 | const ranges = terms |
| 239 | .map((term) => { |
| 240 | const index = lowerText.indexOf(term); |
| 241 | return index === -1 ? undefined : [index, index + term.length] as const; |
| 242 | }) |
| 243 | .filter((range): range is readonly [number, number] => range !== undefined) |
| 244 | .sort((a, b) => a[0] - b[0]); |
| 245 | |
| 246 | if (ranges.length === 0) { |
| 247 | element.textContent = text; |
| 248 | return; |
| 249 | } |
| 250 | |
| 251 | let cursor = 0; |
| 252 | for (const [start, end] of ranges) { |
| 253 | if (start < cursor) continue; |
| 254 | if (start > cursor) { |
| 255 | element.append(document.createTextNode(text.slice(cursor, start))); |
| 256 | } |
| 257 | const mark = document.createElement("mark"); |
| 258 | mark.textContent = text.slice(start, end); |
| 259 | element.append(mark); |
| 260 | cursor = end; |
| 261 | } |
| 262 | if (cursor < text.length) { |
| 263 | element.append(document.createTextNode(text.slice(cursor))); |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | function resultMeta(item: SearchIndexItem) { |
| 268 | if (item.type === "model") { |
no test coverage detected