* Applies search highlighting to already syntax-highlighted HTML. * Wraps matches in spans with appropriate highlighting classes. * * @param html - The syntax-highlighted HTML string * @param searchQuery - The search query to highlight * @param currentMatchIndex - Index of the current match (fo
(
html: string,
searchQuery: string,
currentMatchIndex: number,
matchCounter: { count: number }
)
| 843 | * @returns The HTML with search highlighting applied |
| 844 | */ |
| 845 | function applySearchHighlighting( |
| 846 | html: string, |
| 847 | searchQuery: string, |
| 848 | currentMatchIndex: number, |
| 849 | matchCounter: { count: number } |
| 850 | ): string { |
| 851 | if (!searchQuery.trim()) return html |
| 852 | |
| 853 | const escaped = escapeRegex(searchQuery) |
| 854 | const regex = new RegExp(`(${escaped})`, 'gi') |
| 855 | |
| 856 | // We need to be careful not to match inside HTML tags |
| 857 | // Split by HTML tags and only process text parts |
| 858 | const parts = html.split(/(<[^>]+>)/g) |
| 859 | |
| 860 | return parts |
| 861 | .map((part) => { |
| 862 | // If it's an HTML tag, don't modify it |
| 863 | if (part.startsWith('<') && part.endsWith('>')) { |
| 864 | return part |
| 865 | } |
| 866 | |
| 867 | // Process text content |
| 868 | return part.replace(regex, (match) => { |
| 869 | const isCurrentMatch = matchCounter.count === currentMatchIndex |
| 870 | matchCounter.count++ |
| 871 | |
| 872 | const bgClass = isCurrentMatch |
| 873 | ? 'bg-[var(--highlight-search-active)] text-[var(--text-primary)]' |
| 874 | : 'bg-[#FCD34D]/40 dark:bg-[#FCD34D]/30' |
| 875 | |
| 876 | return `<mark class="${bgClass} rounded-xs" data-search-match>${match}</mark>` |
| 877 | }) |
| 878 | }) |
| 879 | .join('') |
| 880 | } |
| 881 | |
| 882 | /** |
| 883 | * Props for inner viewer components (with defaults already applied). |
no test coverage detected