96 lines
2.9 KiB
JavaScript
96 lines
2.9 KiB
JavaScript
(function(){
|
|
function buildTokens(rawQuery) {
|
|
const q = (rawQuery || '').trim();
|
|
if (!q) return [];
|
|
// Normalize punctuation to spaces, trim non-alphanumerics at ends, dedupe
|
|
const tokens = q
|
|
.replace(/[,_;:]+/g, ' ')
|
|
.split(/\s+/)
|
|
.map(t => t.replace(/^[^A-Za-z0-9]+|[^A-Za-z0-9]+$/g, ''))
|
|
.filter(Boolean);
|
|
return Array.from(new Set(tokens));
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
try {
|
|
return (window.htmlSanitizer && window.htmlSanitizer.escape)
|
|
? window.htmlSanitizer.escape(text)
|
|
: String(text == null ? '' : text);
|
|
} catch (_) {
|
|
return String(text == null ? '' : text);
|
|
}
|
|
}
|
|
|
|
function highlight(text, tokens) {
|
|
const value = text == null ? '' : String(text);
|
|
if (!value || !Array.isArray(tokens) || tokens.length === 0) return escapeHtml(value);
|
|
try {
|
|
const source = String(value);
|
|
const haystack = source.toLowerCase();
|
|
const uniqueTokens = Array.from(new Set((tokens || []).map(t => String(t).toLowerCase()).filter(Boolean)));
|
|
const ranges = [];
|
|
uniqueTokens.forEach(t => {
|
|
let from = 0;
|
|
while (from <= haystack.length - t.length && t.length > 0) {
|
|
const idx = haystack.indexOf(t, from);
|
|
if (idx === -1) break;
|
|
ranges.push([idx, idx + t.length]);
|
|
from = idx + 1; // allow overlapping matches shift by 1
|
|
}
|
|
});
|
|
if (ranges.length === 0) return escapeHtml(source);
|
|
// Merge overlapping/adjacent ranges
|
|
ranges.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
|
|
const merged = [];
|
|
let [curStart, curEnd] = ranges[0];
|
|
for (let i = 1; i < ranges.length; i++) {
|
|
const [s, e] = ranges[i];
|
|
if (s <= curEnd) {
|
|
// overlap or adjacency
|
|
curEnd = Math.max(curEnd, e);
|
|
} else {
|
|
merged.push([curStart, curEnd]);
|
|
[curStart, curEnd] = [s, e];
|
|
}
|
|
}
|
|
merged.push([curStart, curEnd]);
|
|
// Build output with escaping of text segments
|
|
let out = '';
|
|
let pos = 0;
|
|
merged.forEach(([s, e]) => {
|
|
if (pos < s) out += escapeHtml(source.slice(pos, s));
|
|
out += '<strong>' + escapeHtml(source.slice(s, e)) + '</strong>';
|
|
pos = e;
|
|
});
|
|
if (pos < source.length) out += escapeHtml(source.slice(pos));
|
|
return out;
|
|
} catch (_) {
|
|
return escapeHtml(String(text));
|
|
}
|
|
}
|
|
|
|
function formatSnippet(snippet, tokens) {
|
|
if (!snippet) return '';
|
|
let html = String(snippet);
|
|
try {
|
|
const hasStrong = /<\s*strong\b/i.test(html);
|
|
if (!hasStrong) {
|
|
html = highlight(html, Array.isArray(tokens) ? tokens : []);
|
|
}
|
|
if (window.htmlSanitizer && typeof window.htmlSanitizer.sanitize === 'function') {
|
|
html = window.htmlSanitizer.sanitize(html);
|
|
}
|
|
} catch (_) {}
|
|
return html;
|
|
}
|
|
|
|
window.highlightUtils = {
|
|
buildTokens,
|
|
highlight,
|
|
escape: escapeHtml,
|
|
formatSnippet
|
|
};
|
|
})();
|
|
|
|
|