let selectedSearchResult = 0;
function setupSearchSystem() {
createSearchModal();
console.log('🔍 Search system initialized');
}
function createSearchModal() {
const searchModalHTML = `
<div id="search-modal" class="search-modal" style="display: none;">
<div class="search-backdrop" onclick="closeSearch()"></div>
<div class="search-container">
<div class="search-header">
<div class="search-input-wrapper">
<span class="search-icon">🔍</span>
<input type="text" id="search-input" placeholder="Search documentation..." autocomplete="off">
<span class="search-shortcut">ESC</span>
</div>
</div>
<div class="search-results" id="search-results">
<div class="search-empty">
<span class="search-empty-icon">📖</span>
<p>Start typing to search documentation...</p>
<div class="search-tips">
<span class="tip">Try: "plugin", "theme", "AI", "deployment"</span>
</div>
</div>
</div>
<div class="search-footer">
<div class="search-footer-hints">
<span class="hint"><kbd>↑↓</kbd> Navigate</span>
<span class="hint"><kbd>Enter</kbd> Open</span>
<span class="hint"><kbd>Esc</kbd> Close</span>
</div>
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', searchModalHTML);
const searchInput = document.getElementById('search-input');
searchInput.addEventListener('input', handleSearchInput);
searchInput.addEventListener('keydown', handleSearchKeyboard);
}
function toggleSearch() {
if (isSearchOpen) {
closeSearch();
} else {
openSearch();
}
}
function openSearch() {
const modal = document.getElementById('search-modal');
const input = document.getElementById('search-input');
if (modal && input) {
isSearchOpen = true;
modal.style.display = 'flex';
requestAnimationFrame(() => {
modal.classList.add('search-modal-open');
});
setTimeout(() => {
input.focus();
}, 150);
document.body.classList.add('search-open');
console.log('🔍 Search opened');
}
}
function closeSearch() {
const modal = document.getElementById('search-modal');
const input = document.getElementById('search-input');
if (modal && input) {
isSearchOpen = false;
modal.classList.remove('search-modal-open');
setTimeout(() => {
modal.style.display = 'none';
input.value = '';
clearSearchResults();
}, 200);
document.body.classList.remove('search-open');
console.log('🔍 Search closed');
}
}
function handleSearchInput(e) {
const query = e.target.value.trim();
if (query.length === 0) {
showEmptySearch();
return;
}
if (query.length < 2) {
return;
}
const results = performSearch(query);
displaySearchResults(results, query);
}
function handleSearchKeyboard(e) {
const resultsContainer = document.getElementById('search-results');
const results = resultsContainer.querySelectorAll('.search-result-item');
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
selectedSearchResult = Math.min(selectedSearchResult + 1, results.length - 1);
updateSearchSelection(results);
break;
case 'ArrowUp':
e.preventDefault();
selectedSearchResult = Math.max(selectedSearchResult - 1, 0);
updateSearchSelection(results);
break;
case 'Enter':
e.preventDefault();
const selectedResult = results[selectedSearchResult];
if (selectedResult) {
const pageId = selectedResult.dataset.page;
closeSearch();
navigateToPage(pageId);
}
break;
case 'Escape':
e.preventDefault();
closeSearch();
break;
}
}
function performSearch(query) {
const normalizedQuery = query.toLowerCase();
const results = [];
for (const entry of searchIndex) {
const score = calculateSearchScore(entry, normalizedQuery);
if (score > 0) {
results.push({
...entry,
searchScore: score
});
}
}
results.sort((a, b) => {
const typePriority = { page: 4, heading: 3, command: 2, content: 1 };
const aPriority = typePriority[a.type] || 0;
const bPriority = typePriority[b.type] || 0;
if (aPriority !== bPriority) {
return bPriority - aPriority;
}
return b.searchScore - a.searchScore;
});
return results.slice(0, 10); }
function calculateSearchScore(entry, query) {
const title = entry.title.toLowerCase();
const content = entry.content.toLowerCase();
let score = 0;
if (title === query) {
score += 100;
}
if (title.startsWith(query)) {
score += 80;
}
if (title.includes(query)) {
score += 60;
}
if (content.includes(query)) {
score += 40;
}
const titleFuzzyScore = calculateFuzzyScore(title, query);
const contentFuzzyScore = calculateFuzzyScore(content, query);
score += titleFuzzyScore * 2; score += contentFuzzyScore;
score += entry.score || 0;
return score;
}
function calculateFuzzyScore(text, query) {
if (!text || !query) return 0;
const textChars = text.split('');
const queryChars = query.split('');
let score = 0;
let queryIndex = 0;
let consecutiveMatches = 0;
for (let i = 0; i < textChars.length && queryIndex < queryChars.length; i++) {
if (textChars[i] === queryChars[queryIndex]) {
score += 1 + consecutiveMatches;
consecutiveMatches++;
queryIndex++;
} else {
consecutiveMatches = 0;
}
}
if (queryIndex === queryChars.length) {
score += 20;
}
return score;
}
function displaySearchResults(results, query) {
const container = document.getElementById('search-results');
selectedSearchResult = 0;
if (results.length === 0) {
container.innerHTML = `
<div class="search-empty">
<span class="search-empty-icon">🤷♂️</span>
<p>No results found for "${query}"</p>
<div class="search-tips">
<span class="tip">Try different keywords or check spelling</span>
</div>
</div>
`;
return;
}
const resultsHTML = results.map((result, index) => {
const pageTitle = pages[result.page]?.title || result.page;
const typeIcon = getResultTypeIcon(result.type);
const highlightedTitle = highlightSearchTerms(result.title, query);
const highlightedContent = highlightSearchTerms(result.content, query);
return `
<div class="search-result-item ${index === 0 ? 'selected' : ''}"
data-page="${result.page}"
onclick="selectSearchResult('${result.page}')">
<div class="search-result-icon">${typeIcon}</div>
<div class="search-result-content">
<div class="search-result-title">${highlightedTitle}</div>
<div class="search-result-description">${highlightedContent}</div>
<div class="search-result-page">${pageTitle}</div>
</div>
<div class="search-result-type">${result.type}</div>
</div>
`;
}).join('');
container.innerHTML = resultsHTML;
}
function showEmptySearch() {
const container = document.getElementById('search-results');
container.innerHTML = `
<div class="search-empty">
<span class="search-empty-icon">📖</span>
<p>Start typing to search documentation...</p>
<div class="search-tips">
<span class="tip">Try: "plugin", "theme", "AI", "deployment"</span>
</div>
</div>
`;
}
function clearSearchResults() {
selectedSearchResult = 0;
showEmptySearch();
}
function updateSearchSelection(results) {
results.forEach((result, index) => {
result.classList.toggle('selected', index === selectedSearchResult);
});
const selectedResult = results[selectedSearchResult];
if (selectedResult) {
selectedResult.scrollIntoView({
behavior: 'smooth',
block: 'nearest'
});
}
}
function selectSearchResult(pageId) {
closeSearch();
navigateToPage(pageId);
}
function getResultTypeIcon(type) {
const icons = {
page: '📄',
heading: '📝',
command: '⌨️',
content: '📋'
};
return icons[type] || '📋';
}
function highlightSearchTerms(text, query) {
if (!query || !text) return text;
const regex = new RegExp(`(${escapeRegex(query)})`, 'gi');
return text.replace(regex, '<mark>$1</mark>');
}
function escapeRegex(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
window.toggleSearch = toggleSearch;
window.openSearch = openSearch;
window.closeSearch = closeSearch;
window.setupSearchSystem = setupSearchSystem;
window.selectSearchResult = selectSearchResult;