Files
menav/src/script.js
2025-10-19 01:49:15 +08:00

1323 lines
49 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 全局MeNav对象 - 用于浏览器扩展
window.MeNav = {
version: "1.0.0",
// 获取配置数据
getConfig: function() {
const configData = document.getElementById('menav-config-data');
return configData ? JSON.parse(configData.textContent) : null;
},
// 获取元素的唯一标识符
_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) {
// 优先更新站点卡片中的回退图标favicon模式下存在
const iconElement = element.querySelector('i.icon-fallback') || element.querySelector('i');
if (iconElement) {
iconElement.className = newData.icon;
}
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 = `<i class="${newData.icon || iconClass}"></i> ${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;
// 创建新的站点卡片
const newSite = document.createElement('a');
newSite.className = 'site-card';
newSite.href = data.url || '#';
newSite.title = data.name + (data.description ? ' - ' + data.description : '');
// 设置数据属性
newSite.setAttribute('data-type', 'site');
newSite.setAttribute('data-name', data.name || '未命名站点');
newSite.setAttribute('data-url', data.url || '');
newSite.setAttribute('data-icon', data.icon || 'fas fa-link');
newSite.setAttribute('data-description', data.description || '');
// 添加内容(根据图标模式渲染)
try {
const cfg = window.MeNav && typeof window.MeNav.getConfig === 'function' ? window.MeNav.getConfig() : null;
const iconsMode = (cfg && (cfg.data?.icons?.mode || cfg.icons?.mode)) || 'favicon';
if (iconsMode === 'favicon' && data.url && /^https?:\/\//i.test(data.url)) {
const faviconUrl = `https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodeURIComponent(data.url)}&size=32`;
newSite.innerHTML = `
<i class="fas fa-circle-notch fa-spin icon-placeholder" aria-hidden="true"></i>
<img class="favicon-icon" src="${faviconUrl}" alt="${(data.name || '站点')} favicon" loading="lazy" style="opacity:0;"
onload="this.style.opacity='1'; this.previousElementSibling.style.display='none';"
onerror="this.style.display='none'; this.previousElementSibling.style.display='none'; this.nextElementSibling.style.display='inline-block';" />
<i class="fas fa-link icon-fallback" aria-hidden="true" style="display:none;"></i>
<h3>${data.name || '未命名站点'}</h3>
<p>${data.description || ''}</p>
`;
} else {
newSite.innerHTML = `
<i class="${data.icon || 'fas fa-link'}"></i>
<h3>${data.name || '未命名站点'}</h3>
<p>${data.description || ''}</p>
`;
}
} catch (e) {
newSite.innerHTML = `
<i class="${data.icon || 'fas fa-link'}"></i>
<h3>${data.name || '未命名站点'}</h3>
<p>${data.description || ''}</p>
`;
}
// 添加到DOM
sitesGrid.appendChild(newSite);
// 移除"暂无网站"提示(如果存在)
const emptyMessage = sitesGrid.querySelector('.empty-sites');
if (emptyMessage) {
emptyMessage.remove();
}
// 触发元素添加事件
this.events.emit('elementAdded', {
id: data.name,
type: 'site',
parentId: parentId,
data: data
});
return data.name;
} 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 = `
<h2 data-editable="category-name"><i class="${data.icon || 'fas fa-folder'}"></i> ${data.name || '未命名分类'}</h2>
<div class="sites-grid" data-container="sites">
<p class="empty-sites">暂无网站</p>
</div>
`;
// 添加到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;
}
}
};
document.addEventListener('DOMContentLoaded', () => {
// 先声明所有状态变量
let isSearchActive = false;
let currentPageId = 'home';
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: '本地搜索',
icon: 'fas fa-search',
url: null // 本地搜索不需要URL
},
google: {
name: 'Google搜索',
icon: 'fab fa-google',
url: 'https://www.google.com/search?q='
},
bing: {
name: 'Bing搜索',
icon: 'fab fa-microsoft',
url: 'https://www.bing.com/search?q='
},
baidu: {
name: '百度搜索',
icon: 'fas fa-paw',
url: 'https://www.baidu.com/s?wd='
}
};
// 获取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 searchIcon = document.querySelector('.search-icon');
const searchEngineToggle = document.querySelector('.search-engine-toggle');
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 {
const title = card.querySelector('h3')?.textContent?.toLowerCase() || '';
const description = card.querySelector('p')?.textContent?.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);
// 检查是否是移动设备
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 {
const title = card.querySelector('h3');
const description = card.querySelector('p');
if (!title || !description) return;
// 安全地高亮标题中的匹配文本
if (title.textContent.toLowerCase().includes(searchTerm)) {
const titleText = title.textContent;
const regex = new RegExp(`(${escapeRegExp(searchTerm)})`, 'gi');
// 创建安全的DOM结构而不是直接使用innerHTML
const titleFragment = document.createDocumentFragment();
let lastIndex = 0;
let match;
// 使用正则表达式查找所有匹配项
const titleRegex = new RegExp(regex);
while ((match = titleRegex.exec(titleText)) !== null) {
// 添加匹配前的文本
if (match.index > lastIndex) {
titleFragment.appendChild(document.createTextNode(
titleText.substring(lastIndex, match.index)
));
}
// 添加高亮的匹配文本
const span = document.createElement('span');
span.className = 'highlight';
span.textContent = match[0];
titleFragment.appendChild(span);
lastIndex = match.index + match[0].length;
// 防止无限循环
if (titleRegex.lastIndex === 0) break;
}
// 添加剩余文本
if (lastIndex < titleText.length) {
titleFragment.appendChild(document.createTextNode(
titleText.substring(lastIndex)
));
}
// 清空原标题并添加新内容
while (title.firstChild) {
title.removeChild(title.firstChild);
}
title.appendChild(titleFragment);
} else if (PinyinMatch.match(title.textContent, searchTerm)) {
const arr = PinyinMatch.match(title.textContent, searchTerm);
const [start, end] = arr;
title.innerHTML = title.textContent.slice(0, start) +
`<span class="highlight">${title.textContent.slice(start, end + 1)}</span>` +
title.textContent.slice(end + 1);
}
// 安全地高亮描述中的匹配文本
if (description.textContent.toLowerCase().includes(searchTerm)) {
const descText = description.textContent;
const regex = new RegExp(`(${escapeRegExp(searchTerm)})`, 'gi');
// 创建安全的DOM结构而不是直接使用innerHTML
const descFragment = document.createDocumentFragment();
let lastIndex = 0;
let match;
// 使用正则表达式查找所有匹配项
const descRegex = new RegExp(regex);
while ((match = descRegex.exec(descText)) !== null) {
// 添加匹配前的文本
if (match.index > lastIndex) {
descFragment.appendChild(document.createTextNode(
descText.substring(lastIndex, match.index)
));
}
// 添加高亮的匹配文本
const span = document.createElement('span');
span.className = 'highlight';
span.textContent = match[0];
descFragment.appendChild(span);
lastIndex = match.index + match[0].length;
// 防止无限循环
if (descRegex.lastIndex === 0) break;
}
// 添加剩余文本
if (lastIndex < descText.length) {
descFragment.appendChild(document.createTextNode(
descText.substring(lastIndex)
));
}
// 清空原描述并添加新内容
while (description.firstChild) {
description.removeChild(description.firstChild);
}
description.appendChild(descFragment);
} else if (PinyinMatch.match(description.textContent, searchTerm)) {
const arr = PinyinMatch.match(description.textContent, searchTerm);
const [start, end] = arr;
description.innerHTML = description.textContent.slice(0, start) +
`<span class="highlight">${description.textContent.slice(start, end + 1)}</span>` +
description.textContent.slice(end + 1);
}
} 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 = 'home';
pages.forEach(page => {
page.classList.toggle('active', page.id === 'home');
});
}
} 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 = () => {
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 (searchIcon) {
searchIcon.addEventListener('click', (e) => {
e.stopPropagation();
toggleEngineDropdown();
});
}
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();
// 关闭下拉菜单
searchEngineDropdown.classList.remove('active');
if (searchBox) {
searchBox.classList.remove('dropdown-open');
}
}
});
});
// 点击页面其他位置关闭下拉菜单
document.addEventListener('click', () => {
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');
}
});
// 更新搜索图标以反映当前搜索引擎
if (searchIcon) {
// 清除所有类保留基本的search-icon类
const classList = searchIcon.className.split(' ').filter(cls => cls === 'search-icon');
searchIcon.className = classList.join(' ');
// 添加当前搜索引擎的图标类
const engine = searchEngines[currentSearchEngine];
if (engine) {
const iconClasses = engine.icon.split(' ');
iconClasses.forEach(cls => {
searchIcon.classList.add(cls);
});
// 更新标题提示
searchIcon.setAttribute('title', 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('home');
// 添加载入动画
categories.forEach((category, index) => {
setTimeout(() => {
category.style.opacity = '1';
}, index * 100);
});
// 初始展开当前页面的子菜单
const activeNavItem = document.querySelector('.nav-item.active');
if (activeNavItem) {
const activeWrapper = activeNavItem.closest('.nav-item-wrapper');
if (activeWrapper) {
}
}
// 导航项点击效果
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();
}
}
});
});
// 初始化搜索索引使用requestIdleCallback或setTimeout延迟初始化避免影响页面加载
if ('requestIdleCallback' in window) {
requestIdleCallback(() => initSearchIndex());
} else {
setTimeout(initSearchIndex, 1000);
}
});
});