diff --git a/src/lib/utils/highlightText.ts b/src/lib/utils/highlightText.ts new file mode 100644 index 0000000..d033033 --- /dev/null +++ b/src/lib/utils/highlightText.ts @@ -0,0 +1,36 @@ +/** + * Wraps matching text in tags for highlighting. + * Returns HTML string to be used with {@html} in Svelte. + * + * @param text - The text to search within + * @param query - The search query to highlight + * @returns HTML string with matches wrapped in tags + */ +export function highlightText(text: string, query: string): string { + // Don't highlight if query is too short + if (!query || query.length < 2) return escapeHtml(text); + + // Escape HTML in the original text first + const escaped = escapeHtml(text); + + // Escape regex special characters in query + const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const regex = new RegExp(`(${escapedQuery})`, 'gi'); + + // Wrap matches in with bold styling (no background per CONTEXT.md) + return escaped.replace(regex, '$1'); +} + +/** + * Escapes HTML special characters to prevent XSS. + */ +function escapeHtml(text: string): string { + const map: Record = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + return text.replace(/[&<>"']/g, (char) => map[char]); +}