/** * ResultRenderer Module * Handles rendering of search results in the interface */ class ResultRenderer { constructor(options, utils) { this.options = options; this.utils = utils; } /** * Render search results */ render(results, query, container) { if (!container) { console.warn('No container provided for rendering results'); return; } if (results.length === 0) { container.innerHTML = this.renderNoResults(query); return; } const html = results.map((result, index) => { const isSelected = index === 0; return this.renderResultItem(result, query, isSelected); }).join(''); container.innerHTML = `
${html}
`; // Bind click events this.bindResultEvents(container, results); } /** * Render a single result item */ renderResultItem(result, query, isSelected = false) { const title = this.utils.highlightText(result.title || 'Untitled', query); const summary = this.utils.highlightText(result.summary || result.content?.substring(0, 200) || '', query); const breadcrumb = this.utils.generateBreadcrumb(result.id); // Render matching sections const sectionsHtml = this.renderMatchingSections(result, query); // Show multiple matches indicator const multipleMatchesIndicator = result.totalMatches > 1 ? `${result.totalMatches} matches` : ''; return `
${title} ${multipleMatchesIndicator}
${summary}...
${sectionsHtml}
${breadcrumb} ${result.tags ? `${this.utils.safeArray(result.tags).slice(0, 3).map(tag => `${tag}`).join('')}` : ''}
`; } /** * Render matching sections within a result */ renderMatchingSections(result, query) { if (!result.matchingSections || result.matchingSections.length <= 1) { return ''; } // Show only the first few sections to avoid overwhelming const sectionsToShow = result.matchingSections.slice(0, 4); const hasMore = result.matchingSections.length > 4; const sectionsHtml = sectionsToShow.map(section => { const icon = this.utils.getSectionIcon(section.type, section.level); const sectionText = this.utils.highlightText(section.text, query); const anchor = section.anchor ? `#${section.anchor}` : ''; return `
${icon} ${sectionText}
`; }).join(''); const moreIndicator = hasMore ? `
+${result.matchingSections.length - 4} more sections
` : ''; return `
${sectionsHtml} ${moreIndicator}
`; } /** * Render no results state */ renderNoResults(query) { return `

No results found for "${this.utils.escapeHtml(query)}"

Try:
`; } /** * Bind click events to result items */ bindResultEvents(container, results) { container.querySelectorAll('.search-result-item').forEach((item, index) => { const result = results[index]; // Main item click - go to document item.addEventListener('click', (e) => { // Don't trigger if clicking on a section if (e.target.closest('.search-result-section')) { return; } const url = item.dataset.url; window.location.href = url; }); // Section clicks - go to specific section item.querySelectorAll('.search-result-section').forEach(sectionEl => { sectionEl.addEventListener('click', (e) => { e.stopPropagation(); const anchor = sectionEl.dataset.anchor; const baseUrl = item.dataset.url; window.location.href = baseUrl + anchor; }); }); }); } /** * Get result items from container */ getResultItems(container) { return container.querySelectorAll('.search-result-item'); } /** * Get selected result item */ getSelectedResult(container) { return container.querySelector('.search-result-item.selected'); } /** * Select next result item */ selectNext(container) { const results = this.getResultItems(container); const selected = this.getSelectedResult(container); if (results.length === 0) return; if (!selected) { results[0].classList.add('selected'); return; } const currentIndex = Array.from(results).indexOf(selected); selected.classList.remove('selected'); const nextIndex = (currentIndex + 1) % results.length; results[nextIndex].classList.add('selected'); results[nextIndex].scrollIntoView({ block: 'nearest' }); } /** * Select previous result item */ selectPrevious(container) { const results = this.getResultItems(container); const selected = this.getSelectedResult(container); if (results.length === 0) return; if (!selected) { results[results.length - 1].classList.add('selected'); return; } const currentIndex = Array.from(results).indexOf(selected); selected.classList.remove('selected'); const prevIndex = currentIndex === 0 ? results.length - 1 : currentIndex - 1; results[prevIndex].classList.add('selected'); results[prevIndex].scrollIntoView({ block: 'nearest' }); } /** * Activate selected result */ activateSelected(container) { const selected = this.getSelectedResult(container); if (selected) { selected.click(); } } /** * Clear all selections */ clearSelection(container) { const results = this.getResultItems(container); results.forEach(result => result.classList.remove('selected')); } /** * Render loading state */ renderLoading(container) { if (container) { container.innerHTML = `

Searching...

`; } } /** * Render error state */ renderError(container, message = 'Search error occurred') { if (container) { container.innerHTML = `

${this.utils.escapeHtml(message)}

`; } } } // Make ResultRenderer available globally window.ResultRenderer = ResultRenderer;