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.
This commit is contained in:
@@ -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) => `<span class="hextra-search-match">${match}</span>`);
|
||||
// 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(`
|
||||
<div class="hextra-search-prefix">${result.prefix}</div>`));
|
||||
const prefix = document.createElement('div');
|
||||
prefix.className = 'hextra-search-prefix';
|
||||
prefix.textContent = result.prefix;
|
||||
fragment.appendChild(prefix);
|
||||
}
|
||||
let li = createElement(`
|
||||
<li>
|
||||
<a data-index="${i}" href="${result.route}" class=${i === 0 ? "hextra-search-active" : ""}>
|
||||
<div class="hextra-search-title">`+ highlightMatches(result.children.title, query) + `</div>` +
|
||||
(result.children.content ?
|
||||
`<div class="hextra-search-excerpt">` + highlightMatches(result.children.content, query) + `</div>` : '') + `
|
||||
</a>
|
||||
</li>`);
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user