From a9ef7ad57dcf2a04d7e85691c58b840b9e52163f Mon Sep 17 00:00:00 2001 From: Xin <5097752+imfing@users.noreply.github.com> Date: Sun, 18 Jan 2026 19:59:09 +0000 Subject: [PATCH] fix(flexsearch): enhance match highlighting and safe DOM manipulation (#903) - Replaced the highlightMatches function with appendHighlightedText to improve match highlighting using safe text nodes. - Updated DOM manipulation for search results to create elements directly instead of using innerHTML, enhancing security and performance. - Ensured consistent handling of empty text and query cases in the new highlighting function. --- assets/js/flexsearch.js | 77 +++++++++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/assets/js/flexsearch.js b/assets/js/flexsearch.js index b75d461..ccfed4b 100644 --- a/assets/js/flexsearch.js +++ b/assets/js/flexsearch.js @@ -392,18 +392,36 @@ document.addEventListener("DOMContentLoaded", function () { return; } - // Highlight the query in the result text. - function highlightMatches(text, query) { - const escapedQuery = query.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&'); - const regex = new RegExp(escapedQuery, 'gi'); - return text.replace(regex, (match) => `${match}`); - } + // Append text with highlighted matches using safe text nodes. + function appendHighlightedText(container, text, query) { + if (!text) return; + if (!query) { + container.textContent = text; + return; + } - // Create a DOM element from the HTML string. - function createElement(str) { - const div = document.createElement('div'); - div.innerHTML = str.trim(); - return div.firstChild; + const escapedQuery = query.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&'); + if (!escapedQuery) { + container.textContent = text; + return; + } + + const regex = new RegExp(escapedQuery, 'gi'); + let lastIndex = 0; + let match; + while ((match = regex.exec(text)) !== null) { + if (match.index > lastIndex) { + container.appendChild(document.createTextNode(text.slice(lastIndex, match.index))); + } + const span = document.createElement('span'); + span.className = 'hextra-search-match'; + span.textContent = match[0]; + container.appendChild(span); + lastIndex = match.index + match[0].length; + } + if (lastIndex < text.length) { + container.appendChild(document.createTextNode(text.slice(lastIndex))); + } } function handleMouseMove(e) { @@ -421,20 +439,35 @@ document.addEventListener("DOMContentLoaded", function () { for (let i = 0; i < results.length; i++) { const result = results[i]; if (result.prefix) { - fragment.appendChild(createElement(` -