(text: string, query: string)
| 3 | * Returns an HTML string safe for use with dangerouslySetInnerHTML. |
| 4 | */ |
| 5 | export function highlight(text: string, query: string): string { |
| 6 | if (!query.trim()) return escapeHtml(text); |
| 7 | |
| 8 | const terms = tokenize(query); |
| 9 | if (terms.length === 0) return escapeHtml(text); |
| 10 | |
| 11 | // Build a regex that matches any of the terms (case-insensitive) |
| 12 | const pattern = terms |
| 13 | .map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")) |
| 14 | .join("|"); |
| 15 | const regex = new RegExp(`(${pattern})`, "gi"); |
| 16 | |
| 17 | return escapeHtml(text).replace( |
| 18 | // Re-run on escaped HTML — we need to match original terms |
| 19 | // So instead: split on matches then reassemble |
| 20 | regex, |
| 21 | (match) => `<mark class="search-highlight">${match}</mark>` |
| 22 | ); |
| 23 | } |
| 24 | |
| 25 | /** |
| 26 | * Returns a short excerpt (up to maxLength chars) centred around the first match. |
no test coverage detected