coming together
This commit is contained in:
95
static/js/highlight.js
Normal file
95
static/js/highlight.js
Normal file
@@ -0,0 +1,95 @@
|
||||
(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
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user