From 6dbe660a8e0bb8bcd073fa1561985f29e8b6afb6 Mon Sep 17 00:00:00 2001 From: Thomas Richter Date: Sat, 31 Jan 2026 17:12:43 +0100 Subject: [PATCH] feat(05-02): create highlightText utility function - Wrap matching text in tags with bold styling - Escape HTML before highlighting to prevent XSS - 2-character minimum query requirement - Uses bg-transparent (no background per CONTEXT.md) --- src/lib/utils/highlightText.ts | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/lib/utils/highlightText.ts 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]); +}