// Theme Toggle (function() { const toggle = document.getElementById('theme-toggle'); const iconLight = document.getElementById('theme-icon-light'); const iconDark = document.getElementById('theme-icon-dark'); const html = document.documentElement; // Get initial theme function getTheme() { const saved = localStorage.getItem('theme'); if (saved) return saved; if (window.matchMedia('(prefers-color-scheme: dark)').matches) return 'dark'; return 'light'; } // Apply theme function applyTheme(theme) { html.setAttribute('data-theme', theme); localStorage.setItem('theme', theme); updateIcons(theme); } // Update icons function updateIcons(theme) { if (theme === 'dark') { iconLight?.classList.remove('hidden'); iconDark?.classList.add('hidden'); } else { iconLight?.classList.add('hidden'); iconDark?.classList.remove('hidden'); } } // Initialize applyTheme(getTheme()); // Toggle handler toggle?.addEventListener('click', () => { const current = html.getAttribute('data-theme'); applyTheme(current === 'dark' ? 'light' : 'dark'); }); })(); // Mobile Menu (function() { const menuToggle = document.getElementById('mobile-menu-toggle'); const mobileMenu = document.getElementById('mobile-menu'); menuToggle?.addEventListener('click', () => { mobileMenu?.classList.toggle('hidden'); }); })(); // Search (function() { const searchToggle = document.getElementById('search-toggle'); const searchModal = document.getElementById('search-modal'); const searchBackdrop = document.getElementById('search-backdrop'); const searchInput = document.getElementById('search-input'); const searchResults = document.getElementById('search-results'); const searchCount = document.getElementById('search-count'); let fuse = null; let searchIndex = null; // Load search index async function loadSearchIndex() { if (searchIndex) return searchIndex; try { const response = await fetch('/index.json'); searchIndex = await response.json(); fuse = new Fuse(searchIndex, { keys: ['title', 'content', 'tags'], threshold: 0.3, includeMatches: true, }); return searchIndex; } catch (err) { console.error('Failed to load search index:', err); return []; } } // Open search function openSearch() { searchModal?.classList.remove('hidden'); document.body.style.overflow = 'hidden'; searchInput?.focus(); loadSearchIndex(); } // Close search function closeSearch() { searchModal?.classList.add('hidden'); document.body.style.overflow = ''; if (searchInput) searchInput.value = ''; searchResults!.innerHTML = '
输入关键词开始搜索...
'; searchCount!.textContent = '0'; } // Perform search function performSearch(query) { if (!query || !fuse) { searchResults!.innerHTML = '
输入关键词开始搜索...
'; searchCount!.textContent = '0'; return; } const results = fuse.search(query); searchCount!.textContent = results.length.toString(); if (results.length === 0) { searchResults!.innerHTML = '
未找到相关结果
'; return; } const html = results.map(result => { const item = result.item; const matches = result.matches || []; // Highlight matches in title let title = item.title; matches.filter(m => m.key === 'title').forEach(m => { m.indices.forEach(([start, end]) => { const matched = title.slice(start, end + 1); title = title.slice(0, start) + `${matched}` + title.slice(end + 1); }); }); // Get summary with highlighted content let summary = item.summary || item.content?.slice(0, 150) + '...' || ''; matches.filter(m => m.key === 'content').forEach(m => { if (m.indices[0]) { const [start, end] = m.indices[0]; const contextStart = Math.max(0, start - 50); const contextEnd = Math.min(item.content.length, end + 50); summary = '...' + item.content.slice(contextStart, contextEnd) + '...'; m.indices.slice(0, 2).forEach(([s, e]) => { const relativeStart = s - contextStart + 3; const relativeEnd = e - contextStart + 3; if (relativeStart >= 0 && relativeEnd < summary.length) { const matched = summary.slice(relativeStart, relativeEnd + 1); summary = summary.slice(0, relativeStart) + `${matched}` + summary.slice(relativeEnd + 1); } }); } }); return `
${title}
${summary}
${item.date} · ${item.category}
`; }).join(''); searchResults!.innerHTML = html; } // Event listeners searchToggle?.addEventListener('click', openSearch); searchBackdrop?.addEventListener('click', closeSearch); document.addEventListener('keydown', (e) => { if (e.key === 'k' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); openSearch(); } if (e.key === 'Escape') { closeSearch(); } }); // Debounced search input let debounceTimer; searchInput?.addEventListener('input', (e) => { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { performSearch(e.target.value.trim()); }, 300); }); })();