/**
* Utils Module
* Contains utility functions used across the enhanced search system
*/
class Utils {
constructor() {
// Utility class - no initialization needed
}
/**
* Debounce function to limit rapid function calls
*/
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
/**
* Escape special regex characters
*/
escapeRegex(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/**
* Escape HTML to prevent XSS attacks
*/
escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
/**
* Highlight search terms in text
*/
highlightText(text, query, highlightClass = 'search-highlight') {
if (!query || !text) return text;
const terms = query.toLowerCase().split(/\s+/);
let highlighted = text;
terms.forEach(term => {
if (term.length > 1) {
const regex = new RegExp(`(${this.escapeRegex(term)})`, 'gi');
highlighted = highlighted.replace(regex, `$1`);
}
});
return highlighted;
}
/**
* Generate breadcrumb from document ID
*/
generateBreadcrumb(docId) {
const parts = docId.split('/').filter(part => part && part !== 'index');
return parts.length > 0 ? parts.join(' › ') : 'Home';
}
/**
* Generate anchor link from heading text (Sphinx-style)
*/
generateAnchor(headingText) {
return headingText
.toLowerCase()
.replace(/[^\w\s-]/g, '') // Remove special chars
.replace(/\s+/g, '-') // Replace spaces with hyphens
.trim();
}
/**
* Get document URL from result object
*/
getDocumentUrl(result) {
if (result.url) {
return result.url;
}
return `${result.id.replace(/^\/+/, '')}.html`;
}
/**
* Get appropriate icon for section type
*/
getSectionIcon(type, level) {
switch (type) {
case 'title':
return '';
case 'heading':
if (level <= 2) return '';
if (level <= 4) return '';
return '';
case 'content':
return '';
default:
return '';
}
}
/**
* Load external script (like Lunr.js)
*/
async loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
/**
* Safe substring with fallback
*/
safeSubstring(str, maxLength = 200, fallback = '') {
if (!str) return fallback;
return str.length > maxLength ? str.substring(0, maxLength) : str;
}
/**
* Check if string is valid and not empty
*/
isValidString(str) {
return typeof str === 'string' && str.trim().length > 0;
}
/**
* Safe array access with fallback
*/
safeArray(arr, fallback = []) {
return Array.isArray(arr) ? arr : fallback;
}
}
// Make Utils available globally
window.Utils = Utils;