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(` -
${result.prefix}
`)); + const prefix = document.createElement('div'); + prefix.className = 'hextra-search-prefix'; + prefix.textContent = result.prefix; + fragment.appendChild(prefix); } - let li = createElement(` -
  • - -
    `+ highlightMatches(result.children.title, query) + `
    ` + - (result.children.content ? - `
    ` + highlightMatches(result.children.content, query) + `
    ` : '') + ` -
    -
  • `); + const li = document.createElement('li'); + const link = document.createElement('a'); + link.dataset.index = i; + link.href = result.route; + if (i === 0) { + link.classList.add('hextra-search-active'); + } + + const title = document.createElement('div'); + title.className = 'hextra-search-title'; + appendHighlightedText(title, result.children.title, query); + link.appendChild(title); + + if (result.children.content) { + const excerpt = document.createElement('div'); + excerpt.className = 'hextra-search-excerpt'; + appendHighlightedText(excerpt, result.children.content, query); + link.appendChild(excerpt); + } + + li.appendChild(link); li.addEventListener('mousemove', handleMouseMove); li.addEventListener('keydown', handleKeyDown); - li.querySelector('a').addEventListener('click', finishSearch); + link.addEventListener('click', finishSearch); fragment.appendChild(li); } resultsElement.appendChild(fragment);