/** * 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;