Poprawka w rozszerzeniu pliku highlightUtils
This commit is contained in:
134
src/lib/highlightUtils.jsx
Normal file
134
src/lib/highlightUtils.jsx
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Utility functions do podświetlania wyszukiwanych fraz
|
||||
* Używane przez komponenty search
|
||||
*/
|
||||
|
||||
/**
|
||||
* Escape RegExp special characters
|
||||
*/
|
||||
export function escapeRegExp(str) {
|
||||
return String(str).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Podświetlenie w czystym tekście (np. title)
|
||||
*
|
||||
* @param {string} text - Tekst do podświetlenia
|
||||
* @param {string} query - Fraza do wyszukania
|
||||
* @returns {string|Array} - Tekst lub array z elementami JSX
|
||||
*
|
||||
* @example
|
||||
* highlightText("Hello World", "world")
|
||||
* // => ["Hello ", <mark class="f-hl">World</mark>]
|
||||
*/
|
||||
export function highlightText(text, query) {
|
||||
const trimmedQuery = (query || '').trim();
|
||||
if (!trimmedQuery) return text;
|
||||
|
||||
const re = new RegExp(escapeRegExp(trimmedQuery), 'ig');
|
||||
const parts = String(text || '').split(re);
|
||||
|
||||
if (parts.length === 1) return text;
|
||||
|
||||
// split() gubi match – więc budujemy przez exec/match
|
||||
const matches = String(text || '').match(re) || [];
|
||||
|
||||
const result = [];
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
result.push(parts[i]);
|
||||
if (i < matches.length) {
|
||||
result.push(<mark class="f-hl">{matches[i]}</mark>);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Podświetlenie wewnątrz HTML (po markdown)
|
||||
* Omija tagi PRE, CODE, SCRIPT, STYLE
|
||||
*
|
||||
* @param {string} html - HTML do podświetlenia
|
||||
* @param {string} query - Fraza do wyszukania
|
||||
* @returns {string} - HTML z podświetleniem
|
||||
*
|
||||
* @example
|
||||
* const html = "<p>Hello World</p>";
|
||||
* highlightHtml(html, "world")
|
||||
* // => "<p>Hello <mark class="f-hl">World</mark></p>"
|
||||
*/
|
||||
export function highlightHtml(html, query) {
|
||||
const trimmedQuery = (query || '').trim();
|
||||
if (!trimmedQuery) return html;
|
||||
|
||||
const re = new RegExp(escapeRegExp(trimmedQuery), 'ig');
|
||||
|
||||
const doc = new DOMParser().parseFromString(html, 'text/html');
|
||||
const root = doc.body;
|
||||
|
||||
const walker = doc.createTreeWalker(root, NodeFilter.SHOW_TEXT, null);
|
||||
|
||||
// Funkcja sprawdzająca czy node jest w tagu do pominięcia
|
||||
const shouldSkipNode = (node) => {
|
||||
const parent = node.parentElement;
|
||||
if (!parent) return true;
|
||||
|
||||
const tagName = parent.tagName;
|
||||
return tagName === 'SCRIPT' ||
|
||||
tagName === 'STYLE' ||
|
||||
tagName === 'CODE' ||
|
||||
tagName === 'PRE';
|
||||
};
|
||||
|
||||
// Zbierz wszystkie text nodes
|
||||
const textNodes = [];
|
||||
let node;
|
||||
while ((node = walker.nextNode())) {
|
||||
textNodes.push(node);
|
||||
}
|
||||
|
||||
// Podświetl każdy text node
|
||||
for (const textNode of textNodes) {
|
||||
if (shouldSkipNode(textNode)) continue;
|
||||
|
||||
const text = textNode.nodeValue || '';
|
||||
if (!re.test(text)) continue;
|
||||
|
||||
// Reset RegExp state (test() z /g/ przesuwa lastIndex)
|
||||
re.lastIndex = 0;
|
||||
|
||||
const fragment = doc.createDocumentFragment();
|
||||
let lastIndex = 0;
|
||||
let match;
|
||||
|
||||
while ((match = re.exec(text))) {
|
||||
const matchStart = match.index;
|
||||
const matchEnd = matchStart + match[0].length;
|
||||
|
||||
// Tekst przed match
|
||||
if (matchStart > lastIndex) {
|
||||
fragment.appendChild(
|
||||
doc.createTextNode(text.slice(lastIndex, matchStart))
|
||||
);
|
||||
}
|
||||
|
||||
// Match w <mark>
|
||||
const mark = doc.createElement('mark');
|
||||
mark.className = 'f-hl';
|
||||
mark.textContent = text.slice(matchStart, matchEnd);
|
||||
fragment.appendChild(mark);
|
||||
|
||||
lastIndex = matchEnd;
|
||||
}
|
||||
|
||||
// Tekst po ostatnim match
|
||||
if (lastIndex < text.length) {
|
||||
fragment.appendChild(doc.createTextNode(text.slice(lastIndex)));
|
||||
}
|
||||
|
||||
// Zastąp text node fragmentem
|
||||
textNode.parentNode?.replaceChild(fragment, textNode);
|
||||
}
|
||||
|
||||
return root.innerHTML;
|
||||
}
|
||||
Reference in New Issue
Block a user