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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Highlight the query in the result text.
|
// Append text with highlighted matches using safe text nodes.
|
||||||
function highlightMatches(text, query) {
|
function appendHighlightedText(container, text, query) {
|
||||||
const escapedQuery = query.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
|
if (!text) return;
|
||||||
const regex = new RegExp(escapedQuery, 'gi');
|
if (!query) {
|
||||||
return text.replace(regex, (match) => `<span class="hextra-search-match">${match}</span>`);
|
container.textContent = text;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a DOM element from the HTML string.
|
const escapedQuery = query.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||||
function createElement(str) {
|
if (!escapedQuery) {
|
||||||
const div = document.createElement('div');
|
container.textContent = text;
|
||||||
div.innerHTML = str.trim();
|
return;
|
||||||
return div.firstChild;
|
}
|
||||||
|
|
||||||
|
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) {
|
function handleMouseMove(e) {
|
||||||
@@ -421,20 +439,35 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
for (let i = 0; i < results.length; i++) {
|
for (let i = 0; i < results.length; i++) {
|
||||||
const result = results[i];
|
const result = results[i];
|
||||||
if (result.prefix) {
|
if (result.prefix) {
|
||||||
fragment.appendChild(createElement(`
|
const prefix = document.createElement('div');
|
||||||
<div class="hextra-search-prefix">${result.prefix}</div>`));
|
prefix.className = 'hextra-search-prefix';
|
||||||
|
prefix.textContent = result.prefix;
|
||||||
|
fragment.appendChild(prefix);
|
||||||
}
|
}
|
||||||
let li = createElement(`
|
const li = document.createElement('li');
|
||||||
<li>
|
const link = document.createElement('a');
|
||||||
<a data-index="${i}" href="${result.route}" class=${i === 0 ? "hextra-search-active" : ""}>
|
link.dataset.index = i;
|
||||||
<div class="hextra-search-title">`+ highlightMatches(result.children.title, query) + `</div>` +
|
link.href = result.route;
|
||||||
(result.children.content ?
|
if (i === 0) {
|
||||||
`<div class="hextra-search-excerpt">` + highlightMatches(result.children.content, query) + `</div>` : '') + `
|
link.classList.add('hextra-search-active');
|
||||||
</a>
|
}
|
||||||
</li>`);
|
|
||||||
|
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('mousemove', handleMouseMove);
|
||||||
li.addEventListener('keydown', handleKeyDown);
|
li.addEventListener('keydown', handleKeyDown);
|
||||||
li.querySelector('a').addEventListener('click', finishSearch);
|
link.addEventListener('click', finishSearch);
|
||||||
fragment.appendChild(li);
|
fragment.appendChild(li);
|
||||||
}
|
}
|
||||||
resultsElement.appendChild(fragment);
|
resultsElement.appendChild(fragment);
|
||||||
|
|||||||
Reference in New Issue
Block a user