function menavExtractDomain(url) { if (!url) return ''; try { // 移除协议部分 (http://, https://, etc.) let domain = String(url).replace(/^[a-zA-Z]+:\/\//, ''); // 移除路径、查询参数和锚点 domain = domain.split('/')[0].split('?')[0].split('#')[0]; // 移除端口号(如果有) domain = domain.split(':')[0]; return domain; } catch (e) { return String(url); } } // 修复移动端 `100vh` 视口高度问题:用实际可视高度驱动布局,避免侧边栏/内容区底部被浏览器 UI 遮挡 function menavUpdateAppHeight() { const viewportHeight = window.visualViewport ? window.visualViewport.height : window.innerHeight; document.documentElement.style.setProperty('--app-height', `${Math.round(viewportHeight)}px`); } menavUpdateAppHeight(); window.addEventListener('resize', menavUpdateAppHeight); if (window.visualViewport) { window.visualViewport.addEventListener('resize', menavUpdateAppHeight); } // 配置数据缓存:避免浏览器扩展/站点脚本频繁 JSON.parse let menavConfigCacheReady = false; let menavConfigCacheRaw = null; let menavConfigCacheValue = null; // 全局MeNav对象 - 用于浏览器扩展 window.MeNav = { version: "1.0.0", // 获取配置数据 getConfig: function(options) { const configData = document.getElementById('menav-config-data'); if (!configData) return null; const raw = configData.textContent || ''; if (!menavConfigCacheReady || menavConfigCacheRaw !== raw) { menavConfigCacheValue = JSON.parse(raw); menavConfigCacheRaw = raw; menavConfigCacheReady = true; } if (options && options.clone) { if (typeof structuredClone === 'function') { return structuredClone(menavConfigCacheValue); } return JSON.parse(JSON.stringify(menavConfigCacheValue)); } return menavConfigCacheValue; }, // 获取元素的唯一标识符 _getElementId: function(element) { const type = element.getAttribute('data-type'); if (type === 'nav-item') { return element.getAttribute('data-id'); } else if (type === 'social-link') { return element.getAttribute('data-url'); } else { return element.getAttribute('data-name'); } }, // 根据类型和ID查找元素 _findElement: function(type, id) { let selector; if (type === 'nav-item') { selector = `[data-type="${type}"][data-id="${id}"]`; } else if (type === 'social-link') { selector = `[data-type="${type}"][data-url="${id}"]`; } else { selector = `[data-type="${type}"][data-name="${id}"]`; } return document.querySelector(selector); }, // 更新DOM元素 updateElement: function(type, id, newData) { const element = this._findElement(type, id); if (!element) return false; if (type === 'site') { // 更新站点卡片 if (newData.url) { element.href = newData.url; element.setAttribute('data-url', newData.url); } if (newData.name) { element.querySelector('h3').textContent = newData.name; element.setAttribute('data-name', newData.name); } if (newData.description) { element.querySelector('p').textContent = newData.description; element.setAttribute('data-description', newData.description); } if (newData.icon) { const iconElement = element.querySelector('i.icon-fallback') || element.querySelector('i.site-icon') || element.querySelector('.site-card-icon i') || element.querySelector('i'); if (iconElement) { const nextIconClass = String(newData.icon || '').trim(); const preservedClasses = []; if (iconElement.classList.contains('icon-fallback')) { preservedClasses.push('icon-fallback'); } if (iconElement.classList.contains('site-icon')) { preservedClasses.push('site-icon'); } if (nextIconClass) { iconElement.className = nextIconClass; preservedClasses.forEach(cls => iconElement.classList.add(cls)); } } element.setAttribute('data-icon', newData.icon); } if (newData.title) element.title = newData.title; // 触发元素更新事件 this.events.emit('elementUpdated', { id: id, type: 'site', data: newData }); return true; } else if (type === 'category') { // 更新分类 if (newData.name) { const titleElement = element.querySelector('h2'); if (titleElement) { // 保留图标 const iconElement = titleElement.querySelector('i'); const iconClass = iconElement ? iconElement.className : ''; titleElement.innerHTML = ` ${newData.name}`; } element.setAttribute('data-name', newData.name); } if (newData.icon) { element.setAttribute('data-icon', newData.icon); } // 触发元素更新事件 this.events.emit('elementUpdated', { id: id, type: 'category', data: newData }); return true; } else if (type === 'nav-item') { // 更新导航项 if (newData.name) { const textElement = element.querySelector('.nav-text'); if (textElement) { textElement.textContent = newData.name; } element.setAttribute('data-name', newData.name); } if (newData.icon) { const iconElement = element.querySelector('i'); if (iconElement) { iconElement.className = newData.icon; } element.setAttribute('data-icon', newData.icon); } // 触发元素更新事件 this.events.emit('elementUpdated', { id: id, type: 'nav-item', data: newData }); return true; } else if (type === 'social-link') { // 更新社交链接 if (newData.url) { element.href = newData.url; element.setAttribute('data-url', newData.url); } if (newData.name) { const textElement = element.querySelector('.nav-text'); if (textElement) { textElement.textContent = newData.name; } element.setAttribute('data-name', newData.name); } if (newData.icon) { const iconElement = element.querySelector('i'); if (iconElement) { iconElement.className = newData.icon; } element.setAttribute('data-icon', newData.icon); } // 触发元素更新事件 this.events.emit('elementUpdated', { id: id, type: 'social-link', data: newData }); return true; } return false; }, // 添加新元素 addElement: function(type, parentId, data) { let parent; if (type === 'site') { // 查找父级分类 parent = document.querySelector(`[data-type="category"][data-name="${parentId}"]`); if (!parent) return null; // 添加站点卡片到分类 const sitesGrid = parent.querySelector('[data-container="sites"]'); if (!sitesGrid) return null; // 站点卡片样式:根据“页面模板”决定(friends/articles/projects 等) let siteCardStyle = ''; try { const pageEl = parent.closest('.page'); const pageId = pageEl && pageEl.id ? String(pageEl.id) : ''; let templateName = pageId; const cfg = window.MeNav && typeof window.MeNav.getConfig === 'function' ? window.MeNav.getConfig() : null; const pageConfig = cfg && cfg.data && pageId ? cfg.data[pageId] : null; if (pageConfig && pageConfig.template) { templateName = String(pageConfig.template); } // projects 模板使用代码仓库风格卡片(与生成端 templates/components/site-card.hbs 保持一致) if (templateName === 'projects') siteCardStyle = 'repo'; } catch (e) { siteCardStyle = ''; } // 创建新的站点卡片 const newSite = document.createElement('a'); newSite.className = siteCardStyle ? `site-card site-card-${siteCardStyle}` : 'site-card'; const siteName = data.name || '未命名站点'; const siteUrl = data.url || '#'; const siteIcon = data.icon || 'fas fa-link'; const siteDescription = data.description || (data.url ? menavExtractDomain(data.url) : ''); const siteFaviconUrl = data && data.faviconUrl ? String(data.faviconUrl).trim() : ''; const siteForceIconModeRaw = data && data.forceIconMode ? String(data.forceIconMode).trim() : ''; const siteForceIconMode = siteForceIconModeRaw === 'manual' || siteForceIconModeRaw === 'favicon' ? siteForceIconModeRaw : ''; newSite.href = siteUrl; newSite.title = siteName + (siteDescription ? ' - ' + siteDescription : ''); if (/^https?:\/\//i.test(siteUrl)) { newSite.target = '_blank'; newSite.rel = 'noopener'; } // 设置数据属性 newSite.setAttribute('data-type', 'site'); newSite.setAttribute('data-name', siteName); newSite.setAttribute('data-url', data.url || ''); newSite.setAttribute('data-icon', siteIcon); if (siteFaviconUrl) newSite.setAttribute('data-favicon-url', siteFaviconUrl); if (siteForceIconMode) newSite.setAttribute('data-force-icon-mode', siteForceIconMode); newSite.setAttribute('data-description', siteDescription); // projects repo 风格:与模板中的 repo 结构保持一致(不走 favicon 逻辑) if (siteCardStyle === 'repo') { const repoHeader = document.createElement('div'); repoHeader.className = 'repo-header'; const repoIcon = document.createElement('i'); repoIcon.className = `${siteIcon || 'fas fa-code'} repo-icon`; repoIcon.setAttribute('aria-hidden', 'true'); const repoTitle = document.createElement('div'); repoTitle.className = 'repo-title'; repoTitle.textContent = siteName; repoHeader.appendChild(repoIcon); repoHeader.appendChild(repoTitle); const repoDesc = document.createElement('div'); repoDesc.className = 'repo-desc'; repoDesc.textContent = siteDescription; newSite.appendChild(repoHeader); newSite.appendChild(repoDesc); const hasStats = data && (data.language || data.stars || data.forks || data.issues); if (hasStats) { const repoStats = document.createElement('div'); repoStats.className = 'repo-stats'; if (data.language) { const languageItem = document.createElement('div'); languageItem.className = 'stat-item'; const langDot = document.createElement('span'); langDot.className = 'lang-dot'; langDot.style.backgroundColor = data.languageColor || '#909296'; languageItem.appendChild(langDot); languageItem.appendChild(document.createTextNode(String(data.language))); repoStats.appendChild(languageItem); } if (data.stars) { const starsItem = document.createElement('div'); starsItem.className = 'stat-item'; const starIcon = document.createElement('i'); starIcon.className = 'far fa-star'; starIcon.setAttribute('aria-hidden', 'true'); starsItem.appendChild(starIcon); starsItem.appendChild(document.createTextNode(` ${data.stars}`)); repoStats.appendChild(starsItem); } if (data.forks) { const forksItem = document.createElement('div'); forksItem.className = 'stat-item'; const forkIcon = document.createElement('i'); forkIcon.className = 'fas fa-code-branch'; forkIcon.setAttribute('aria-hidden', 'true'); forksItem.appendChild(forkIcon); forksItem.appendChild(document.createTextNode(` ${data.forks}`)); repoStats.appendChild(forksItem); } if (data.issues) { const issuesItem = document.createElement('div'); issuesItem.className = 'stat-item'; const issueIcon = document.createElement('i'); issueIcon.className = 'fas fa-exclamation-circle'; issueIcon.setAttribute('aria-hidden', 'true'); issuesItem.appendChild(issueIcon); issuesItem.appendChild(document.createTextNode(` ${data.issues}`)); repoStats.appendChild(issuesItem); } newSite.appendChild(repoStats); } } else { // 添加内容(根据图标模式渲染,避免 innerHTML 注入) const iconWrapper = document.createElement('div'); iconWrapper.className = 'site-card-icon'; iconWrapper.setAttribute('aria-hidden', 'true'); const contentWrapper = document.createElement('div'); contentWrapper.className = 'site-card-content'; const titleEl = document.createElement('h3'); titleEl.textContent = siteName; const descEl = document.createElement('p'); descEl.textContent = siteDescription; contentWrapper.appendChild(titleEl); contentWrapper.appendChild(descEl); let iconsMode = 'favicon'; try { const cfg = window.MeNav && typeof window.MeNav.getConfig === 'function' ? window.MeNav.getConfig() : null; iconsMode = (cfg && (cfg.data?.icons?.mode || cfg.icons?.mode)) || 'favicon'; } catch (e) { iconsMode = 'favicon'; } const shouldUseCustomFavicon = Boolean(siteFaviconUrl); const effectiveIconsMode = siteForceIconMode || iconsMode; if (shouldUseCustomFavicon) { const iconContainer = document.createElement('div'); iconContainer.className = 'icon-container'; const placeholder = document.createElement('i'); placeholder.className = 'fas fa-circle-notch fa-spin icon-placeholder'; placeholder.setAttribute('aria-hidden', 'true'); const fallback = document.createElement('i'); fallback.className = `${siteIcon} icon-fallback`; fallback.setAttribute('aria-hidden', 'true'); const favicon = document.createElement('img'); favicon.className = 'favicon-icon'; favicon.src = siteFaviconUrl; favicon.alt = `${siteName} favicon`; favicon.loading = 'lazy'; favicon.addEventListener('load', () => { favicon.classList.add('loaded'); placeholder.classList.add('hidden'); }); favicon.addEventListener('error', () => { favicon.classList.add('error'); placeholder.classList.add('hidden'); fallback.classList.add('visible'); }); iconContainer.appendChild(placeholder); iconContainer.appendChild(favicon); iconContainer.appendChild(fallback); iconWrapper.appendChild(iconContainer); } else if (effectiveIconsMode === 'favicon' && siteUrl && /^https?:\/\//i.test(siteUrl)) { const faviconUrlPrimary = `https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodeURIComponent(siteUrl)}&size=32`; const faviconUrlFallback = `https://t3.gstatic.cn/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodeURIComponent(siteUrl)}&size=32`; const iconContainer = document.createElement('div'); iconContainer.className = 'icon-container'; const placeholder = document.createElement('i'); placeholder.className = 'fas fa-circle-notch fa-spin icon-placeholder'; placeholder.setAttribute('aria-hidden', 'true'); const fallback = document.createElement('i'); fallback.className = `${siteIcon} icon-fallback`; fallback.setAttribute('aria-hidden', 'true'); const favicon = document.createElement('img'); favicon.className = 'favicon-icon'; favicon.src = faviconUrlPrimary; favicon.alt = `${siteName} favicon`; favicon.loading = 'lazy'; let faviconFallbackTried = false; favicon.addEventListener('load', () => { favicon.classList.add('loaded'); placeholder.classList.add('hidden'); }); favicon.addEventListener('error', () => { if (!faviconFallbackTried) { faviconFallbackTried = true; favicon.src = faviconUrlFallback; return; } favicon.classList.add('error'); placeholder.classList.add('hidden'); fallback.classList.add('visible'); }); iconContainer.appendChild(placeholder); iconContainer.appendChild(favicon); iconContainer.appendChild(fallback); iconWrapper.appendChild(iconContainer); } else { const iconEl = document.createElement('i'); iconEl.className = `${siteIcon} site-icon`; iconEl.setAttribute('aria-hidden', 'true'); iconWrapper.appendChild(iconEl); } newSite.appendChild(iconWrapper); newSite.appendChild(contentWrapper); } // 添加到DOM sitesGrid.appendChild(newSite); // 移除"暂无网站"提示(如果存在) const emptyMessage = sitesGrid.querySelector('.empty-sites'); if (emptyMessage) { emptyMessage.remove(); } // 触发元素添加事件 this.events.emit('elementAdded', { id: siteName, type: 'site', parentId: parentId, data: data }); return siteName; } else if (type === 'category') { // 查找父级页面容器 parent = document.querySelector(`[data-container="categories"]`); if (!parent) return null; // 创建新的分类 const newCategory = document.createElement('section'); newCategory.className = 'category'; // 设置数据属性 newCategory.setAttribute('data-type', 'category'); newCategory.setAttribute('data-name', data.name || '未命名分类'); newCategory.setAttribute('data-icon', data.icon || 'fas fa-folder'); newCategory.setAttribute('data-container', 'categories'); // 添加内容 newCategory.innerHTML = `

${data.name || '未命名分类'}

暂无网站

`; // 添加到DOM parent.appendChild(newCategory); // 触发元素添加事件 this.events.emit('elementAdded', { id: data.name, type: 'category', data: data }); return data.name; } return null; }, // 删除元素 removeElement: function(type, id) { const element = this._findElement(type, id); if (!element) return false; // 获取父级容器(如果是站点卡片) let parentId = null; if (type === 'site') { const categoryElement = element.closest('[data-type="category"]'); if (categoryElement) { parentId = categoryElement.getAttribute('data-name'); } } // 删除元素 element.remove(); // 触发元素删除事件 this.events.emit('elementRemoved', { id: id, type: type, parentId: parentId }); return true; }, // 获取所有元素 getAllElements: function(type) { return Array.from(document.querySelectorAll(`[data-type="${type}"]`)).map(el => { const id = this._getElementId(el); return { id: id, type: type, element: el }; }); }, // 事件系统 events: { listeners: {}, // 添加事件监听器 on: function(event, callback) { if (!this.listeners[event]) { this.listeners[event] = []; } this.listeners[event].push(callback); return this; }, // 触发事件 emit: function(event, data) { if (this.listeners[event]) { this.listeners[event].forEach(callback => callback(data)); } return this; }, // 移除事件监听器 off: function(event, callback) { if (this.listeners[event]) { if (callback) { this.listeners[event] = this.listeners[event].filter(cb => cb !== callback); } else { delete this.listeners[event]; } } return this; } } }; // 多层级嵌套书签功能 function getCollapsibleNestedContainers(root) { if (!root) return []; const headers = root.querySelectorAll( '.category > .category-header[data-toggle="category"], .group > .group-header[data-toggle="group"]' ); return Array.from(headers).map(header => header.parentElement).filter(Boolean); } function isNestedContainerCollapsible(container) { if (!container) return false; if (container.classList.contains('category')) { return Boolean(container.querySelector(':scope > .category-header[data-toggle="category"]')); } if (container.classList.contains('group')) { return Boolean(container.querySelector(':scope > .group-header[data-toggle="group"]')); } return false; } window.MeNav.expandAll = function() { const activePage = document.querySelector('.page.active'); if (activePage) { getCollapsibleNestedContainers(activePage).forEach(element => { element.classList.remove('collapsed'); saveToggleState(element, 'expanded'); }); } }; window.MeNav.collapseAll = function() { const activePage = document.querySelector('.page.active'); if (activePage) { getCollapsibleNestedContainers(activePage).forEach(element => { element.classList.add('collapsed'); saveToggleState(element, 'collapsed'); }); } }; // 智能切换分类展开/收起状态 window.MeNav.toggleCategories = function() { const activePage = document.querySelector('.page.active'); if (!activePage) return; const allElements = getCollapsibleNestedContainers(activePage); const collapsedElements = allElements.filter(element => element.classList.contains('collapsed')); if (allElements.length === 0) return; // 如果收起的数量 >= 总数的一半,执行展开;否则执行收起 if (collapsedElements.length >= allElements.length / 2) { window.MeNav.expandAll(); updateCategoryToggleIcon('up'); } else { window.MeNav.collapseAll(); updateCategoryToggleIcon('down'); } }; // 更新分类切换按钮图标 function updateCategoryToggleIcon(state) { const toggleBtn = document.getElementById('category-toggle'); if (!toggleBtn) return; const icon = toggleBtn.querySelector('i'); if (!icon) return; if (state === 'up') { icon.className = 'fas fa-angle-double-up'; toggleBtn.setAttribute('aria-label', '收起分类'); } else { icon.className = 'fas fa-angle-double-down'; toggleBtn.setAttribute('aria-label', '展开分类'); } } window.MeNav.toggleCategory = function(categoryName, subcategoryName = null, groupName = null, subgroupName = null) { let selector = `[data-name="${categoryName}"]`; if (subcategoryName) selector += ` [data-name="${subcategoryName}"]`; if (groupName) selector += ` [data-name="${groupName}"]`; if (subgroupName) selector += ` [data-name="${subgroupName}"]`; const element = document.querySelector(selector); if (element) { toggleNestedElement(element); } }; window.MeNav.getNestedStructure = function() { // 返回完整的嵌套结构数据 const categories = []; document.querySelectorAll('.category-level-1').forEach(cat => { categories.push(extractNestedData(cat)); }); return categories; }; // 切换嵌套元素 function toggleNestedElement(container) { if (!isNestedContainerCollapsible(container)) return; const isCollapsed = container.classList.contains('collapsed'); if (isCollapsed) { container.classList.remove('collapsed'); saveToggleState(container, 'expanded'); } else { container.classList.add('collapsed'); saveToggleState(container, 'collapsed'); } // 触发自定义事件 const event = new CustomEvent('nestedToggle', { detail: { element: container, type: container.dataset.type, name: container.dataset.name, isCollapsed: !isCollapsed } }); document.dispatchEvent(event); } // 保存切换状态 function saveToggleState(element, state) { const type = element.dataset.type; const name = element.dataset.name; const level = element.dataset.level || '1'; const key = `menav-toggle-${type}-${level}-${name}`; localStorage.setItem(key, state); } // 恢复切换状态 function restoreToggleState(element) { const type = element.dataset.type; const name = element.dataset.name; const level = element.dataset.level || '1'; const key = `menav-toggle-${type}-${level}-${name}`; const savedState = localStorage.getItem(key); if (savedState === 'collapsed') { element.classList.add('collapsed'); } } // 初始化嵌套分类 function initializeNestedCategories() { // 为所有可折叠元素添加切换功能 document.querySelectorAll('[data-toggle="category"], [data-toggle="group"]').forEach(header => { header.addEventListener('click', function(e) { e.stopPropagation(); const container = this.parentElement; toggleNestedElement(container); }); // 恢复保存的状态 restoreToggleState(header.parentElement); }); } // 提取嵌套数据 function extractNestedData(element) { const data = { name: element.dataset.name, type: element.dataset.type, level: element.dataset.level, isCollapsed: element.classList.contains('collapsed') }; // 提取子元素数据 const subcategories = element.querySelectorAll(':scope > .category-content > .subcategories-container > .category'); if (subcategories.length > 0) { data.subcategories = Array.from(subcategories).map(sub => extractNestedData(sub)); } const groups = element.querySelectorAll(':scope > .category-content > .groups-container > .group'); if (groups.length > 0) { data.groups = Array.from(groups).map(group => extractNestedData(group)); } const subgroups = element.querySelectorAll(':scope > .group-content > .subgroups-container > .group'); if (subgroups.length > 0) { data.subgroups = Array.from(subgroups).map(subgroup => extractNestedData(subgroup)); } const sites = element.querySelectorAll(':scope > .category-content > .sites-grid > .site-card, :scope > .group-content > .sites-grid > .site-card'); if (sites.length > 0) { data.sites = Array.from(sites).map(site => ({ name: site.dataset.name, url: site.dataset.url, icon: site.dataset.icon, description: site.dataset.description })); } return data; } document.addEventListener('DOMContentLoaded', () => { // 先声明所有状态变量 let isSearchActive = false; // 首页不再固定为 "home":以导航顺序第一项为准 const homePageId = (() => { // 1) 优先从生成端注入的配置数据读取(保持与实际导航顺序一致) try { const config = window.MeNav && typeof window.MeNav.getConfig === 'function' ? window.MeNav.getConfig() : null; const injectedHomePageId = config && config.data && config.data.homePageId ? String(config.data.homePageId).trim() : ''; if (injectedHomePageId) return injectedHomePageId; const nav = config && config.data && Array.isArray(config.data.navigation) ? config.data.navigation : null; const firstId = nav && nav[0] && nav[0].id ? String(nav[0].id).trim() : ''; if (firstId) return firstId; } catch (error) { // 忽略解析错误,继续使用 DOM 推断 } // 2) 回退到 DOM:取首个导航项的 data-page const firstNavItem = document.querySelector('.nav-item[data-page]'); if (firstNavItem) { const id = String(firstNavItem.getAttribute('data-page') || '').trim(); if (id) return id; } // 3) 最后兜底:取首个页面容器 id const firstPage = document.querySelector('.page[id]'); if (firstPage && firstPage.id) return firstPage.id; return 'home'; })(); let currentPageId = homePageId; let isInitialLoad = true; let isSidebarOpen = false; let isSearchOpen = false; let isLightTheme = false; // 主题状态 let isSidebarCollapsed = false; // 侧边栏折叠状态 let pages; // 页面元素的全局引用 let currentSearchEngine = 'local'; // 当前选择的搜索引擎 // 搜索索引,用于提高搜索效率 let searchIndex = { initialized: false, items: [] }; // 搜索引擎配置 const searchEngines = { local: { name: '本地搜索', iconSvg: ``, url: null // 本地搜索不需要URL }, google: { name: 'Google搜索', iconSvg: ``, url: 'https://www.google.com/search?q=' }, bing: { name: 'Bing搜索', iconSvg: ``, url: 'https://www.bing.com/search?q=' }, duckduckgo: { name: 'DuckDuckGo搜索', shortName: 'duckgo', // DuckDuckGo 使用内联 SVG,避免依赖不存在的 Font Awesome 品牌图标 iconSvg: ``, url: 'https://duckduckgo.com/?q=' } }; // 获取DOM元素 - 基本元素 const searchInput = document.getElementById('search'); const searchBox = document.querySelector('.search-box'); const searchResultsPage = document.getElementById('search-results'); const searchSections = searchResultsPage.querySelectorAll('.search-section'); // 搜索引擎相关元素 const searchEngineToggle = document.querySelector('.search-engine-toggle'); const searchEngineToggleIcon = searchEngineToggle ? searchEngineToggle.querySelector('.search-engine-icon') : null; const searchEngineToggleLabel = searchEngineToggle ? searchEngineToggle.querySelector('.search-engine-label') : null; const searchEngineDropdown = document.querySelector('.search-engine-dropdown'); const searchEngineOptions = document.querySelectorAll('.search-engine-option'); // 移动端元素 const menuToggle = document.querySelector('.menu-toggle'); const searchToggle = document.querySelector('.search-toggle'); const sidebar = document.querySelector('.sidebar'); const searchContainer = document.querySelector('.search-container'); const overlay = document.querySelector('.overlay'); // 侧边栏折叠功能 const sidebarToggle = document.querySelector('.sidebar-toggle'); const content = document.querySelector('.content'); // 主题切换元素 const themeToggle = document.querySelector('.theme-toggle'); const themeIcon = themeToggle.querySelector('i'); // 滚动进度条元素 const scrollProgress = document.querySelector('.scroll-progress'); // 移除预加载类,允许CSS过渡效果 document.documentElement.classList.remove('preload'); // 应用从localStorage读取的主题设置 if (document.documentElement.classList.contains('theme-preload')) { document.documentElement.classList.remove('theme-preload'); document.body.classList.add('light-theme'); isLightTheme = true; } // 应用从localStorage读取的侧边栏状态 if (document.documentElement.classList.contains('sidebar-collapsed-preload')) { document.documentElement.classList.remove('sidebar-collapsed-preload'); sidebar.classList.add('collapsed'); content.classList.add('expanded'); isSidebarCollapsed = true; } // 即时移除loading类,确保侧边栏可见 document.body.classList.remove('loading'); document.body.classList.add('loaded'); // 侧边栏折叠功能 function toggleSidebarCollapse() { // 仅在交互时启用布局相关动画,避免首屏闪烁 document.documentElement.classList.add('with-anim'); isSidebarCollapsed = !isSidebarCollapsed; // 使用 requestAnimationFrame 确保平滑过渡 requestAnimationFrame(() => { sidebar.classList.toggle('collapsed', isSidebarCollapsed); content.classList.toggle('expanded', isSidebarCollapsed); // 保存折叠状态到localStorage localStorage.setItem('sidebarCollapsed', isSidebarCollapsed ? 'true' : 'false'); }); } // 初始化侧边栏折叠状态 - 已在页面加载前处理,此处仅完成图标状态初始化等次要任务 function initSidebarState() { // 从localStorage获取侧边栏状态 const savedState = localStorage.getItem('sidebarCollapsed'); // 图标状态与折叠状态保持一致 if (savedState === 'true' && !isMobile()) { isSidebarCollapsed = true; } else { isSidebarCollapsed = false; } } // 侧边栏折叠按钮点击事件 if (sidebarToggle) { sidebarToggle.addEventListener('click', toggleSidebarCollapse); } // 主题切换功能 function toggleTheme() { isLightTheme = !isLightTheme; document.body.classList.toggle('light-theme', isLightTheme); // 更新图标 if (isLightTheme) { themeIcon.classList.remove('fa-moon'); themeIcon.classList.add('fa-sun'); } else { themeIcon.classList.remove('fa-sun'); themeIcon.classList.add('fa-moon'); } // 保存主题偏好到localStorage localStorage.setItem('theme', isLightTheme ? 'light' : 'dark'); } // 初始化主题 - 已在页面加载前处理,此处仅完成图标状态初始化等次要任务 function initTheme() { // 从localStorage获取主题偏好 const savedTheme = localStorage.getItem('theme'); // 更新图标状态以匹配当前主题 if (savedTheme === 'light') { isLightTheme = true; themeIcon.classList.remove('fa-moon'); themeIcon.classList.add('fa-sun'); } else { isLightTheme = false; themeIcon.classList.remove('fa-sun'); themeIcon.classList.add('fa-moon'); } } // 主题切换按钮点击事件 themeToggle.addEventListener('click', toggleTheme); // 初始化搜索索引 function initSearchIndex() { if (searchIndex.initialized) return; searchIndex.items = []; try { // 为每个页面创建索引 if (!pages) { pages = document.querySelectorAll('.page'); } pages.forEach(page => { if (page.id === 'search-results') return; const pageId = page.id; page.querySelectorAll('.site-card').forEach(card => { try { // 排除“扩展写回影子结构”等不应参与搜索的卡片 if (card.closest('[data-search-exclude="true"]')) return; // 兼容不同页面/卡片样式:优先取可见文本,其次回退到 data-*(确保 projects repo 卡片也能被搜索) const dataTitle = card.dataset?.name || card.getAttribute('data-name') || ''; const dataDescription = card.dataset?.description || card.getAttribute('data-description') || ''; const titleText = card.querySelector('h3')?.textContent || card.querySelector('.repo-title')?.textContent || dataTitle; const descriptionText = card.querySelector('p')?.textContent || card.querySelector('.repo-desc')?.textContent || dataDescription; const title = String(titleText || '').toLowerCase(); const description = String(descriptionText || '').toLowerCase(); const url = card.href || card.getAttribute('href') || '#'; const icon = card.querySelector('i.icon-fallback')?.className || card.querySelector('i')?.className || ''; // 将卡片信息添加到索引中 searchIndex.items.push({ pageId, title, description, url, icon, element: card, // 预先计算搜索文本,提高搜索效率 searchText: (title + ' ' + description).toLowerCase() }); } catch (cardError) { console.error('Error processing card:', cardError); } }); }); searchIndex.initialized = true; } catch (error) { console.error('Error initializing search index:', error); searchIndex.initialized = true; // 防止反复尝试初始化 } } // 移动端菜单切换 function toggleSidebar() { isSidebarOpen = !isSidebarOpen; sidebar.classList.toggle('active', isSidebarOpen); overlay.classList.toggle('active', isSidebarOpen); if (isSearchOpen) { toggleSearch(); } } // 移动端搜索切换 function toggleSearch() { isSearchOpen = !isSearchOpen; searchContainer.classList.toggle('active', isSearchOpen); overlay.classList.toggle('active', isSearchOpen); if (isSearchOpen) { searchInput.focus(); if (isSidebarOpen) { toggleSidebar(); } } } // 关闭所有移动端面板 function closeAllPanels() { if (isSidebarOpen) { toggleSidebar(); } if (isSearchOpen) { toggleSearch(); } } // 移动端事件监听 menuToggle.addEventListener('click', toggleSidebar); searchToggle.addEventListener('click', toggleSearch); overlay.addEventListener('click', closeAllPanels); // 全局快捷键:Ctrl/Cmd + K 聚焦搜索 document.addEventListener('keydown', e => { const key = (e.key || '').toLowerCase(); if (key !== 'k') return; if ((!e.ctrlKey && !e.metaKey) || e.altKey) return; const target = e.target; const isTypingTarget = target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable); if (isTypingTarget && target !== searchInput) return; e.preventDefault(); if (isMobile() && !isSearchOpen) { toggleSearch(); } searchInput && searchInput.focus(); }); // 检查是否是移动设备 function isMobile() { return window.innerWidth <= 768; } // 窗口大小改变时处理 window.addEventListener('resize', () => { if (!isMobile()) { sidebar.classList.remove('active'); searchContainer.classList.remove('active'); overlay.classList.remove('active'); isSidebarOpen = false; isSearchOpen = false; } else { // 在移动设备下,重置侧边栏折叠状态 sidebar.classList.remove('collapsed'); content.classList.remove('expanded'); } // 重新计算滚动进度 updateScrollProgress(); }); // 更新滚动进度条 function updateScrollProgress() { const scrollTop = content.scrollTop || 0; const scrollHeight = content.scrollHeight - content.clientHeight || 1; const scrollPercent = (scrollTop / scrollHeight) * 100; scrollProgress.style.width = scrollPercent + '%'; } // 监听内容区域的滚动事件 content.addEventListener('scroll', updateScrollProgress); // 初始化时更新一次滚动进度 updateScrollProgress(); // 页面切换功能 function showPage(pageId, skipSearchReset = false) { if (currentPageId === pageId && !skipSearchReset && !isInitialLoad) return; currentPageId = pageId; // 使用 RAF 确保动画流畅 requestAnimationFrame(() => { if (!pages) { pages = document.querySelectorAll('.page'); } pages.forEach(page => { const shouldBeActive = page.id === pageId; if (shouldBeActive !== page.classList.contains('active')) { page.classList.toggle('active', shouldBeActive); } }); // 初始加载完成后设置标志 if (isInitialLoad) { isInitialLoad = false; document.body.classList.add('loaded'); } }); // 重置滚动位置并更新进度条 content.scrollTop = 0; updateScrollProgress(); // 只有在非搜索状态下才重置搜索 if (!skipSearchReset) { searchInput.value = ''; resetSearch(); } } // 搜索功能 function performSearch(searchTerm) { // 确保搜索索引已初始化 if (!searchIndex.initialized) { initSearchIndex(); } searchTerm = searchTerm.toLowerCase().trim(); // 如果搜索框为空,重置所有内容 if (!searchTerm) { resetSearch(); return; } if (!isSearchActive) { isSearchActive = true; } try { // 使用搜索索引进行搜索 const searchResults = new Map(); let hasResults = false; // 使用更高效的搜索算法 const matchedItems = searchIndex.items.filter(item => { return item.searchText.includes(searchTerm) || PinyinMatch.match(item.searchText, searchTerm);; }); // 按页面分组结果 matchedItems.forEach(item => { if (!searchResults.has(item.pageId)) { searchResults.set(item.pageId, []); } // 克隆元素以避免修改原始DOM searchResults.get(item.pageId).push(item.element.cloneNode(true)); hasResults = true; }); // 使用requestAnimationFrame批量更新DOM,减少重排重绘 requestAnimationFrame(() => { try { // 清空并隐藏所有搜索区域 searchSections.forEach(section => { try { const grid = section.querySelector('.sites-grid'); if (grid) { grid.innerHTML = ''; // 使用innerHTML清空,比removeChild更高效 } section.style.display = 'none'; } catch (sectionError) { console.error('Error clearing search section'); } }); // 使用DocumentFragment批量添加DOM元素,减少重排 searchResults.forEach((matches, pageId) => { const section = searchResultsPage.querySelector(`[data-section="${pageId}"]`); if (section) { try { const grid = section.querySelector('.sites-grid'); if (grid) { const fragment = document.createDocumentFragment(); matches.forEach(card => { // 高亮匹配文本 highlightSearchTerm(card, searchTerm); fragment.appendChild(card); }); grid.appendChild(fragment); section.style.display = 'block'; } } catch (gridError) { console.error('Error updating search results grid'); } } }); // 更新搜索结果页面状态 const subtitle = searchResultsPage.querySelector('.subtitle'); if (subtitle) { subtitle.textContent = hasResults ? `在所有页面中找到 ${matchedItems.length} 个匹配项` : '未找到匹配的结果'; } // 显示搜索结果页面 if (currentPageId !== 'search-results') { currentPageId = 'search-results'; pages.forEach(page => { page.classList.toggle('active', page.id === 'search-results'); }); } // 更新搜索状态样式 searchBox.classList.toggle('has-results', hasResults); searchBox.classList.toggle('no-results', !hasResults); } catch (uiError) { console.error('Error updating search UI'); } }); } catch (searchError) { console.error('Error performing search'); } } // 高亮搜索匹配文本 function highlightSearchTerm(card, searchTerm) { if (!card || !searchTerm) return; try { // 兼容 projects repo 卡片:title/desc 不一定是 h3/p const titleElement = card.querySelector('h3') || card.querySelector('.repo-title'); const descriptionElement = card.querySelector('p') || card.querySelector('.repo-desc'); const hasPinyinMatch = typeof PinyinMatch !== 'undefined' && PinyinMatch && typeof PinyinMatch.match === 'function'; const applyRangeHighlight = (element, start, end) => { const text = element.textContent || ''; const safeStart = Math.max(0, Math.min(text.length, start)); const safeEnd = Math.max(safeStart, Math.min(text.length - 1, end)); const fragment = document.createDocumentFragment(); fragment.appendChild(document.createTextNode(text.slice(0, safeStart))); const span = document.createElement('span'); span.className = 'highlight'; span.textContent = text.slice(safeStart, safeEnd + 1); fragment.appendChild(span); fragment.appendChild(document.createTextNode(text.slice(safeEnd + 1))); while (element.firstChild) { element.removeChild(element.firstChild); } element.appendChild(fragment); }; const highlightInElement = element => { if (!element) return; const rawText = element.textContent || ''; const lowerText = rawText.toLowerCase(); if (!rawText) return; if (lowerText.includes(searchTerm)) { const regex = new RegExp(`(${escapeRegExp(searchTerm)})`, 'gi'); const fragment = document.createDocumentFragment(); let lastIndex = 0; let match; while ((match = regex.exec(rawText)) !== null) { if (match.index > lastIndex) { fragment.appendChild(document.createTextNode(rawText.substring(lastIndex, match.index))); } const span = document.createElement('span'); span.className = 'highlight'; span.textContent = match[0]; fragment.appendChild(span); lastIndex = match.index + match[0].length; // 防止无限循环 if (regex.lastIndex === 0) break; } if (lastIndex < rawText.length) { fragment.appendChild(document.createTextNode(rawText.substring(lastIndex))); } while (element.firstChild) { element.removeChild(element.firstChild); } element.appendChild(fragment); return; } if (hasPinyinMatch) { const arr = PinyinMatch.match(rawText, searchTerm); if (Array.isArray(arr) && arr.length >= 2) { const [start, end] = arr; applyRangeHighlight(element, start, end); } } }; highlightInElement(titleElement); highlightInElement(descriptionElement); } catch (error) { console.error('Error highlighting search term'); } } // 转义正则表达式特殊字符 function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } // 重置搜索状态 function resetSearch() { if (!isSearchActive) return; isSearchActive = false; try { requestAnimationFrame(() => { try { // 清空搜索结果 searchSections.forEach(section => { try { const grid = section.querySelector('.sites-grid'); if (grid) { while (grid.firstChild) { grid.removeChild(grid.firstChild); } } section.style.display = 'none'; } catch (sectionError) { console.error('Error clearing search section'); } }); // 移除搜索状态样式 searchBox.classList.remove('has-results', 'no-results'); // 恢复到当前激活的页面 const currentActiveNav = document.querySelector('.nav-item.active'); if (currentActiveNav) { const targetPageId = currentActiveNav.getAttribute('data-page'); if (targetPageId && currentPageId !== targetPageId) { currentPageId = targetPageId; pages.forEach(page => { page.classList.toggle('active', page.id === targetPageId); }); } } else { // 如果没有激活的导航项,默认显示首页 currentPageId = homePageId; pages.forEach(page => { page.classList.toggle('active', page.id === homePageId); }); } } catch (resetError) { console.error('Error resetting search UI'); } }); } catch (error) { console.error('Error in resetSearch'); } } // 搜索输入事件(使用防抖) const debounce = (fn, delay) => { let timer = null; return (...args) => { if (timer) clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); timer = null; }, delay); }; }; const debouncedSearch = debounce(performSearch, 300); searchInput.addEventListener('input', (e) => { // 只有在选择了本地搜索时,才在输入时实时显示本地搜索结果 if (currentSearchEngine === 'local') { debouncedSearch(e.target.value); } else { // 对于非本地搜索,重置之前的本地搜索结果(如果有) if (isSearchActive) { resetSearch(); } } }); // 初始化搜索引擎设置 function initSearchEngine() { // 从本地存储获取上次选择的搜索引擎 const savedEngine = localStorage.getItem('searchEngine'); if (savedEngine && searchEngines[savedEngine]) { currentSearchEngine = savedEngine; } // 设置当前搜索引擎的激活状态及图标 updateSearchEngineUI(); // 初始化搜索引擎下拉菜单事件 const toggleEngineDropdown = () => { if (!searchEngineDropdown) return; const next = !searchEngineDropdown.classList.contains('active'); searchEngineDropdown.classList.toggle('active', next); if (searchBox) { searchBox.classList.toggle('dropdown-open', next); } if (searchEngineToggle) { searchEngineToggle.setAttribute('aria-expanded', String(next)); } }; if (searchEngineToggle) { searchEngineToggle.addEventListener('click', (e) => { e.stopPropagation(); toggleEngineDropdown(); }); // 键盘可访问性:Enter/Space 触发 searchEngineToggle.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); toggleEngineDropdown(); } }); } // 点击搜索引擎选项 searchEngineOptions.forEach(option => { // 初始化激活状态 if (option.getAttribute('data-engine') === currentSearchEngine) { option.classList.add('active'); } option.addEventListener('click', (e) => { e.stopPropagation(); // 获取选中的搜索引擎 const engine = option.getAttribute('data-engine'); // 更新当前搜索引擎 if (engine && searchEngines[engine]) { // 如果搜索引擎变更,且之前有活跃的本地搜索结果,重置搜索状态 if (currentSearchEngine !== engine && isSearchActive) { resetSearch(); } currentSearchEngine = engine; localStorage.setItem('searchEngine', engine); // 更新UI显示 updateSearchEngineUI(); // 关闭下拉菜单 if (searchEngineDropdown) { searchEngineDropdown.classList.remove('active'); } if (searchBox) { searchBox.classList.remove('dropdown-open'); } } }); }); // 点击页面其他位置关闭下拉菜单 document.addEventListener('click', () => { if (!searchEngineDropdown) return; searchEngineDropdown.classList.remove('active'); if (searchBox) { searchBox.classList.remove('dropdown-open'); } }); } // 更新搜索引擎UI显示 function updateSearchEngineUI() { // 移除所有选项的激活状态 searchEngineOptions.forEach(option => { option.classList.remove('active'); // 如果是当前选中的搜索引擎,添加激活状态 if (option.getAttribute('data-engine') === currentSearchEngine) { option.classList.add('active'); } }); // 更新搜索引擎按钮(方案B:前缀按钮显示当前引擎) const engine = searchEngines[currentSearchEngine]; if (!engine) return; const displayName = engine.shortName || engine.name.replace(/搜索$/, ''); if (searchEngineToggleIcon) { if (engine.iconSvg) { searchEngineToggleIcon.className = 'search-engine-icon search-engine-icon-svg'; searchEngineToggleIcon.innerHTML = engine.iconSvg; } else { searchEngineToggleIcon.innerHTML = ''; searchEngineToggleIcon.className = `search-engine-icon ${engine.icon}`; } } if (searchEngineToggleLabel) { searchEngineToggleLabel.textContent = displayName; } if (searchEngineToggle) { searchEngineToggle.setAttribute('aria-label', `当前搜索引擎:${engine.name},点击切换`); } } // 执行搜索(根据选择的搜索引擎) function executeSearch(searchTerm) { if (!searchTerm.trim()) return; // 根据当前搜索引擎执行搜索 if (currentSearchEngine === 'local') { // 执行本地搜索 performSearch(searchTerm); } else { // 使用外部搜索引擎 const engine = searchEngines[currentSearchEngine]; if (engine && engine.url) { // 打开新窗口进行搜索 window.open(engine.url + encodeURIComponent(searchTerm), '_blank'); } } } // 搜索框事件处理 searchInput.addEventListener('keyup', (e) => { if (e.key === 'Escape') { searchInput.value = ''; resetSearch(); } else if (e.key === 'Enter') { executeSearch(searchInput.value); // 在移动设备上,执行搜索后自动关闭搜索面板 if (isMobile() && isSearchOpen) { closeAllPanels(); } } }); // 阻止搜索框的回车默认行为 searchInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); } }); // 初始化 window.addEventListener('load', () => { // 获取可能在HTML生成后才存在的DOM元素 const siteCards = document.querySelectorAll('.site-card'); const categories = document.querySelectorAll('.category'); const navItems = document.querySelectorAll('.nav-item'); const navItemWrappers = document.querySelectorAll('.nav-item-wrapper'); const submenuItems = document.querySelectorAll('.submenu-item'); pages = document.querySelectorAll('.page'); // 初始化主题 initTheme(); // 初始化侧边栏状态 initSidebarState(); // 初始化搜索引擎选择 initSearchEngine(); // 初始化MeNav对象版本信息 try { const config = window.MeNav.getConfig(); if (config && config.version) { window.MeNav.version = config.version; console.log('MeNav API initialized with version:', config.version); } } catch (error) { console.error('Error initializing MeNav API:', error); } // 立即执行初始化,不再使用requestAnimationFrame延迟 // 显示首页 showPage(homePageId); // 添加载入动画 categories.forEach((category, index) => { setTimeout(() => { category.style.opacity = '1'; }, index * 100); }); // 初始展开当前页面的子菜单:高亮项如果有子菜单,需要同步展开 document.querySelectorAll('.nav-item.active').forEach(activeItem => { const activeWrapper = activeItem.closest('.nav-item-wrapper'); if (!activeWrapper) return; const hasSubmenu = activeWrapper.querySelector('.submenu'); if (hasSubmenu) { activeWrapper.classList.add('expanded'); } }); // 导航项点击效果 navItems.forEach(item => { item.addEventListener('click', (e) => { if (item.getAttribute('target') === '_blank') return; e.preventDefault(); // 获取当前项的父级wrapper const wrapper = item.closest('.nav-item-wrapper'); const hasSubmenu = wrapper && wrapper.querySelector('.submenu'); // 处理子菜单展开/折叠 if (hasSubmenu) { // 如果点击的导航项已经激活且有子菜单,则切换子菜单展开状态 if (item.classList.contains('active')) { wrapper.classList.toggle('expanded'); } else { // 关闭所有已展开的子菜单 navItemWrappers.forEach(navWrapper => { if (navWrapper !== wrapper) { navWrapper.classList.remove('expanded'); } }); // 展开当前子菜单 wrapper.classList.add('expanded'); } } // 激活导航项 navItems.forEach(nav => { nav.classList.toggle('active', nav === item); }); const pageId = item.getAttribute('data-page'); if (pageId) { showPage(pageId); // 在移动端视图下点击导航项后自动收起侧边栏 if (isMobile() && isSidebarOpen && !hasSubmenu) { closeAllPanels(); } } }); }); // 子菜单项点击效果 submenuItems.forEach(item => { item.addEventListener('click', (e) => { e.preventDefault(); // 获取页面ID和分类名称 const pageId = item.getAttribute('data-page'); const categoryName = item.getAttribute('data-category'); if (pageId) { // 清除所有子菜单项的激活状态 submenuItems.forEach(subItem => { subItem.classList.remove('active'); }); // 激活当前子菜单项 item.classList.add('active'); // 激活相应的导航项 navItems.forEach(nav => { nav.classList.toggle('active', nav.getAttribute('data-page') === pageId); }); // 显示对应页面 showPage(pageId); // 等待页面切换完成后滚动到对应分类 setTimeout(() => { // 查找目标分类元素 const targetPage = document.getElementById(pageId); if (targetPage) { const targetCategory = Array.from(targetPage.querySelectorAll('.category h2')).find( heading => heading.textContent.trim().includes(categoryName) ); if (targetCategory) { // 优化的滚动实现:滚动到使目标分类位于视口1/4处(更靠近顶部位置) try { // 直接获取所需元素和属性,减少重复查询 const contentElement = document.querySelector('.content'); if (contentElement && contentElement.scrollHeight > contentElement.clientHeight) { // 获取目标元素相对于内容区域的位置 const rect = targetCategory.getBoundingClientRect(); const containerRect = contentElement.getBoundingClientRect(); // 计算目标应该在视口中的位置(视口高度的1/4处) const desiredPosition = containerRect.height / 4; // 计算需要滚动的位置 const scrollPosition = contentElement.scrollTop + rect.top - containerRect.top - desiredPosition; // 执行滚动 contentElement.scrollTo({ top: scrollPosition, behavior: 'smooth' }); } else { // 回退到基本滚动方式 targetCategory.scrollIntoView({ behavior: 'smooth', block: 'center' }); } } catch (error) { console.error('Error during scroll:', error); // 回退到基本滚动方式 targetCategory.scrollIntoView({ behavior: 'smooth', block: 'center' }); } } } }, 25); // 延迟时间 // 在移动端视图下点击子菜单项后自动收起侧边栏 if (isMobile() && isSidebarOpen) { closeAllPanels(); } } }); }); // 初始化嵌套分类功能 initializeNestedCategories(); // 初始化分类切换按钮 const categoryToggleBtn = document.getElementById('category-toggle'); if (categoryToggleBtn) { categoryToggleBtn.addEventListener('click', function() { window.MeNav.toggleCategories(); }); } else { console.error('Category toggle button not found'); } // 初始化搜索索引(使用requestIdleCallback或setTimeout延迟初始化,避免影响页面加载) if ('requestIdleCallback' in window) { requestIdleCallback(() => initSearchIndex()); } else { setTimeout(initSearchIndex, 1000); } }); });