/** * 全站搜索功能 * 支持 URL 搜索、文章搜索、Wiki 搜索 */ (function() { 'use strict'; // 搜索数据 let searchData = { urls: [], articles: [], wiki: [] }; // 搜索配置 const config = { minQueryLength: 2, maxResults: 10, debounceDelay: 300 }; // DOM 元素 let searchOverlay = null; let searchInput = null; let searchResults = null; let searchSpinner = null; let isSearchOpen = false; // 初始化搜索 function init() { if (document.querySelector('.search-overlay')) { return; // 已经初始化过 } createSearchOverlay(); loadSearchData(); bindEvents(); } // 创建搜索覆盖层 function createSearchOverlay() { const overlay = document.createElement('div'); overlay.className = 'search-overlay'; overlay.innerHTML = `

输入关键词开始搜索

`; document.body.appendChild(overlay); searchOverlay = overlay; searchInput = overlay.querySelector('.search-input'); searchResults = overlay.querySelector('.search-results'); searchSpinner = overlay.querySelector('.search-spinner'); // 添加样式 addSearchStyles(); } // 添加搜索样式 function addSearchStyles() { if (document.querySelector('#search-styles')) { return; } const style = document.createElement('style'); style.id = 'search-styles'; style.textContent = ` .search-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.8); z-index: 10000; display: none; align-items: flex-start; justify-content: center; padding-top: 10vh; animation: fadeIn 0.2s ease; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .search-overlay.active { display: flex; } .search-container { width: 90%; max-width: 700px; background: white; border-radius: 12px; box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3); overflow: hidden; animation: slideDown 0.3s ease; } @keyframes slideDown { from { transform: translateY(-50px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } .search-header { display: flex; align-items: center; padding: 20px; border-bottom: 1px solid #e5e7eb; gap: 12px; } .search-input { flex: 1; padding: 12px 16px; font-size: 16px; border: 2px solid #e5e7eb; border-radius: 8px; outline: none; transition: border-color 0.2s; } .search-input:focus { border-color: #3b82f6; } .search-close { width: 40px; height: 40px; border: none; background: #f3f4f6; border-radius: 8px; font-size: 24px; cursor: pointer; color: #6b7280; transition: all 0.2s; } .search-close:hover { background: #e5e7eb; color: #1f2937; } .search-spinner { display: none; padding: 40px; text-align: center; } .search-spinner.active { display: block; } .spinner { width: 40px; height: 40px; border: 4px solid #e5e7eb; border-top-color: #3b82f6; border-radius: 50%; animation: spin 0.8s linear infinite; margin: 0 auto; } @keyframes spin { to { transform: rotate(360deg); } } .search-results { max-height: 400px; overflow-y: auto; padding: 0; } .search-empty { text-align: center; padding: 40px 20px; color: #9ca3af; } .search-empty i { font-size: 48px; margin-bottom: 12px; opacity: 0.5; } .search-item { padding: 16px 20px; border-bottom: 1px solid #f3f4f6; cursor: pointer; transition: background 0.2s; text-decoration: none; color: inherit; display: block; } .search-item:hover, .search-item.active { background: #f9fafb; } .search-item-title { font-weight: 600; color: #1f2937; margin-bottom: 4px; } .search-item-url { color: #6b7280; font-size: 13px; margin-bottom: 4px; } .search-item-desc { color: #9ca3af; font-size: 14px; } .search-item-highlight { background: #fef3c7; padding: 0 2px; border-radius: 2px; } .search-category { padding: 12px 20px; font-weight: 600; color: #6b7280; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; background: #f9fafb; } .search-footer { display: flex; justify-content: space-between; align-items: center; padding: 12px 20px; background: #f9fafb; border-top: 1px solid #e5e7eb; font-size: 13px; color: #6b7280; } .dark-mode .search-overlay { background: rgba(0, 0, 0, 0.9); } .dark-mode .search-container { background: #1f2937; box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5); } .dark-mode .search-header { border-bottom-color: #374151; } .dark-mode .search-input { background: #374151; border-color: #4b5563; color: #f9fafb; } .dark-mode .search-input:focus { border-color: #3b82f6; } .dark-mode .search-close { background: #374151; color: #9ca3af; } .dark-mode .search-close:hover { background: #4b5563; color: #f9fafb; } .dark-mode .search-item { border-bottom-color: #374151; } .dark-mode .search-item:hover, .dark-mode .search-item.active { background: #374151; } .dark-mode .search-item-title { color: #f9fafb; } .dark-mode .search-item-url { color: #9ca3af; } .dark-mode .search-item-desc { color: #6b7280; } .dark-mode .search-item-highlight { background: #92400e; } .dark-mode .search-category { background: #374151; color: #9ca3af; } .dark-mode .search-footer { background: #374151; border-top-color: #4b5563; } /* 移动端适配 */ @media (max-width: 768px) { .search-container { width: 95%; margin-top: 10vh; } .search-input { font-size: 14px; } .search-results { max-height: 50vh; } } `; document.head.appendChild(style); } // 加载搜索数据 function loadSearchData() { // 加载导航数据 fetch('/data/nav.json') .then(response => response.json()) .then(data => { data.categories.forEach(category => { category.links.forEach(link => { searchData.urls.push({ title: link.name, url: link.url, category: category.name, type: 'url' }); }); }); }) .catch(error => { console.error('加载导航数据失败:', error); }); // 加载文章数据(这里可以扩展为实际的文章搜索) const articles = document.querySelectorAll('.post-card'); articles.forEach(article => { const title = article.querySelector('.post-title')?.textContent || ''; const url = article.getAttribute('href') || ''; const desc = article.querySelector('.post-excerpt')?.textContent || ''; if (title && url) { searchData.articles.push({ title: title, url: url, description: desc, type: 'article' }); } }); } // 绑定事件 function bindEvents() { // 键盘快捷键 document.addEventListener('keydown', handleKeydown); // 搜索输入 if (searchInput) { searchInput.addEventListener('input', debounce(handleSearch, config.debounceDelay)); searchInput.addEventListener('keydown', handleSearchKeydown); } // 关闭按钮 const closeBtn = searchOverlay?.querySelector('.search-close'); if (closeBtn) { closeBtn.addEventListener('click', closeSearch); } // 点击覆盖层关闭 searchOverlay?.addEventListener('click', (e) => { if (e.target === searchOverlay) { closeSearch(); } }); } // 处理键盘事件 function handleKeydown(e) { // Cmd/Ctrl + K 打开搜索 if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); toggleSearch(); } // ESC 关闭搜索 if (e.key === 'Escape' && isSearchOpen) { closeSearch(); } } // 处理搜索输入 function handleSearchKeydown(e) { const items = searchResults?.querySelectorAll('.search-item'); if (!items.length) return; const activeItem = searchResults.querySelector('.search-item.active'); const currentIndex = Array.from(items).indexOf(activeItem); // 向下箭头 if (e.key === 'ArrowDown') { e.preventDefault(); const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0; setActiveItem(items[nextIndex]); } // 向上箭头 if (e.key === 'ArrowUp') { e.preventDefault(); const prevIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1; setActiveItem(items[prevIndex]); } // Enter 选择结果 if (e.key === 'Enter' && activeItem) { e.preventDefault(); activeItem.click(); } } // 设置激活项 function setActiveItem(item) { searchResults.querySelectorAll('.search-item').forEach(i => { i.classList.remove('active'); }); item.classList.add('active'); item.scrollIntoView({ block: 'nearest' }); } // 执行搜索 function handleSearch() { const query = searchInput.value.trim(); if (!query || query.length < config.minQueryLength) { showEmptyState(); return; } showSpinner(); // 模拟搜索延迟 setTimeout(() => { const results = performSearch(query); displayResults(results); hideSpinner(); }, 300); } // 执行搜索 function performSearch(query) { const results = []; const lowerQuery = query.toLowerCase(); // 搜索 URL const urlResults = searchData.urls.filter(item => item.title.toLowerCase().includes(lowerQuery) || item.category.toLowerCase().includes(lowerQuery) ).slice(0, config.maxResults); if (urlResults.length > 0) { results.push({ category: '网址导航', items: urlResults }); } // 搜索文章 const articleResults = searchData.articles.filter(item => item.title.toLowerCase().includes(lowerQuery) || (item.description && item.description.toLowerCase().includes(lowerQuery)) ).slice(0, config.maxResults); if (articleResults.length > 0) { results.push({ category: '文章', items: articleResults }); } return results; } // 显示搜索结果 function displayResults(results) { if (!results.length) { searchResults.innerHTML = `

未找到匹配 "${escapeHtml(searchInput.value)}" 的结果

`; return; } let html = ''; results.forEach(category => { html += `
${category.category}
`; category.items.forEach(item => { const highlightedTitle = highlightText(item.title, searchInput.value); const highlightedDesc = item.description ? highlightText(item.description, searchInput.value) : ''; html += `
${highlightedTitle}
${item.type === 'url' ? `
${escapeHtml(item.category)}
` : ''} ${highlightedDesc ? `
${highlightedDesc}
` : ''}
`; }); }); searchResults.innerHTML = html; } // 高亮搜索词 function highlightText(text, query) { if (!query) return escapeHtml(text); const regex = new RegExp(`(${escapeRegExp(query)})`, 'gi'); return escapeHtml(text).replace(regex, '$1'); } // 转义正则表达式 function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } // 显示空状态 function showEmptyState() { searchResults.innerHTML = `

输入关键词开始搜索

至少输入 ${config.minQueryLength} 个字符

`; } // 显示/隐藏加载动画 function showSpinner() { if (searchSpinner) { searchSpinner.classList.add('active'); } } function hideSpinner() { if (searchSpinner) { searchSpinner.classList.remove('active'); } } // 打开搜索 function openSearch() { if (searchOverlay) { searchOverlay.classList.add('active'); isSearchOpen = true; if (searchInput) { setTimeout(() => searchInput.focus(), 100); } document.body.style.overflow = 'hidden'; } } // 关闭搜索 function closeSearch() { if (searchOverlay) { searchOverlay.classList.remove('active'); isSearchOpen = false; if (searchInput) { searchInput.value = ''; showEmptyState(); } document.body.style.overflow = ''; } } // 切换搜索 function toggleSearch() { if (isSearchOpen) { closeSearch(); } else { openSearch(); } } // 防抖函数 function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // HTML 转义 function escapeHtml(text) { if (typeof text !== 'string') { return ''; } return text .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } // 暴露全局方法 window.SiteSearch = { init: init, open: openSearch, close: closeSearch, toggle: toggleSearch }; // 自动初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();