Files
delphi-database/static/js/highlight.js
2025-08-13 18:53:35 -05:00

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
};
})();