优化搜索数据规则,优化dom绑定和监听,优化代码样式

This commit is contained in:
lsy 2025-05-05 19:40:03 +08:00
parent f153c65faa
commit afbdc61605
9 changed files with 2293 additions and 1939 deletions

View File

@ -1092,12 +1092,20 @@ const ArticleFilter: React.FC<ArticleFilterProps> = ({ searchParams = {} }) => {
currentPage: 1, currentPage: 1,
date: "all", date: "all",
}; };
// 先更新UI状态 // 先更新UI状态
setActiveFilters(defaultFilters); setActiveFilters(defaultFilters);
// 直接传递重置后的状态给筛选函数并更新URL // 清除URL参数
applyFilters(defaultFilters); if (typeof window !== 'undefined') {
}, []); window.history.pushState({}, "", window.location.pathname);
}
// 如果WASM模块已加载直接调用筛选逻辑以确保实际应用
if (wasmModule && isArticlesLoaded) {
applyFilteringLogic(defaultFilters);
}
}, [wasmModule, isArticlesLoaded]);
// 渲染错误信息 // 渲染错误信息
const renderError = () => ( const renderError = () => (

View File

@ -187,99 +187,101 @@ const breadcrumbs: Breadcrumb[] = pathSegments
</div> </div>
<script is:inline> <script is:inline>
// 返回按钮点击事件处理 // 面包屑返回按钮处理 - 自销毁模式
(function() { (function() {
// 页面导航计数器
let pageNavigationCount = 0;
// 存储事件监听器,便于统一清理 // 跳过非文章页面,只在文章详情页执行
const listeners = []; const isArticlePage = document.querySelector('.back-button');
if (!isArticlePage) {
// 清理按钮事件监听器 return;
function cleanupButtonListeners() {
// 查找所有返回按钮
const buttons = document.querySelectorAll('.back-button');
buttons.forEach(button => {
// 移除所有可能的事件
if (button._clickHandler) {
button.removeEventListener('click', button._clickHandler);
delete button._clickHandler;
} }
// 清除其他可能的事件 // 集中管理所有事件监听器
const otherClickHandlers = button.__backButtonClickHandlers || []; const allListeners = [];
otherClickHandlers.forEach(handler => {
try {
button.removeEventListener('click', handler);
} catch (e) {
// 忽略错误
}
});
// 重置处理函数数组 // 单独保存清理事件的监听器引用
button.__backButtonClickHandlers = []; const cleanupListeners = [];
});
}
// 添加事件监听器并记录,方便后续统一清理 // 添加事件监听器并记录,方便后续统一清理
function addListener(element, eventType, handler, options) { function addListener(element, eventType, handler, options) {
if (!element) return null; if (!element) {
console.warn(`[面包屑尝试为不存在的元素添加事件`);
// 确保先移除可能已存在的同类型事件处理函数 return null;
if (eventType === 'click' && element.classList.contains('back-button')) {
if (element._clickHandler) {
element.removeEventListener('click', element._clickHandler);
}
element._clickHandler = handler;
// 保存到数组中以便清理
if (!element.__backButtonClickHandlers) {
element.__backButtonClickHandlers = [];
}
element.__backButtonClickHandlers.push(handler);
} }
element.addEventListener(eventType, handler, options); element.addEventListener(eventType, handler, options);
listeners.push({ element, eventType, handler, options }); allListeners.push({ element, eventType, handler, options });
return handler; return handler;
} }
// 清理函数 - 移除所有事件监听器 // 统一的清理函数,执行完整清理并自销毁
function cleanup() { function selfDestruct() {
// 先直接从按钮清理事件 // 1. 移除所有普通事件监听器
cleanupButtonListeners(); allListeners.forEach(({ element, eventType, handler, options }) => {
// 移除所有监听器
listeners.forEach(({ element, eventType, handler, options }) => {
try { try {
element.removeEventListener(eventType, handler, options); element.removeEventListener(eventType, handler, options);
} catch (err) { } catch (err) {
// 忽略错误 console.error(`[面包屑移除事件监听器出错:`, err);
} }
}); });
// 清空数组 // 清空监听器数组
listeners.length = 0; allListeners.length = 0;
// 2. 最后移除清理事件监听器自身
cleanupListeners.forEach(({ element, eventType, handler, options }) => {
try {
element.removeEventListener(eventType, handler, options);
} catch (err) {
console.error(`[面包屑移除清理监听器出错:`, err);
}
});
// 清空清理监听器数组
cleanupListeners.length = 0;
} }
// 设置返回按钮事件 // 注册清理事件,并保存引用
function setupBackButton() { function registerCleanupEvents() {
// 确保当前没有活动的返回按钮事件 // 创建一次性事件处理函数
cleanup(); const beforeSwapHandler = () => {
selfDestruct();
};
const beforeUnloadHandler = () => {
selfDestruct();
};
// 添加清理事件监听器并保存引用
document.addEventListener("astro:before-swap", beforeSwapHandler, { once: true });
window.addEventListener("beforeunload", beforeUnloadHandler, { once: true });
// 如果页面使用swup也注册swup相关的清理事件
if (typeof window.swup !== 'undefined') {
document.addEventListener("swup:willReplaceContent", beforeSwapHandler, { once: true });
}
// 保存清理事件引用,用于完全销毁
cleanupListeners.push(
{ element: document, eventType: "astro:before-swap", handler: beforeSwapHandler, options: { once: true } },
{ element: window, eventType: "beforeunload", handler: beforeUnloadHandler, options: { once: true } }
);
if (typeof window.swup !== 'undefined') {
cleanupListeners.push(
{ element: document, eventType: "swup:willReplaceContent", handler: beforeSwapHandler, options: { once: true } }
);
}
}
// 设置返回按钮功能
function setupBackButton() {
const backButton = document.querySelector('.back-button'); const backButton = document.querySelector('.back-button');
if (!backButton) { if (!backButton) {
return; return;
} }
try {
backButton.style.pointerEvents = 'auto';
} catch (e) {
// 忽略样式错误
}
const clickHandler = (e) => { const clickHandler = (e) => {
e.preventDefault(); e.preventDefault();
@ -301,100 +303,43 @@ const breadcrumbs: Breadcrumb[] = pathSegments
addListener(backButton, 'click', clickHandler); addListener(backButton, 'click', clickHandler);
} }
// 注册清理函数 - 确保在每次页面转换前清理事件 // 主初始化函数
function registerCleanup() { function init() {
const cleanupEvents = [ // 注册清理事件
'astro:before-preparation', registerCleanupEvents();
'astro:before-swap',
'astro:beforeload',
'swup:willReplaceContent'
];
// 为每个事件注册一次性清理函数 // 设置返回按钮
cleanupEvents.forEach(eventName => { setupBackButton();
const handler = () => {
cleanup(); // 注册页面加载后的处理函数 - 仅当使用View Transitions或Swup时
if (typeof document.startViewTransition !== 'undefined' || typeof window.swup !== 'undefined') {
// 仅监听一个事件,保持最小侵入性
const pageLoadHandler = () => {
// 检查是否在文章页面
const backButton = document.querySelector('.back-button');
if (backButton) {
// 先销毁已有的所有处理函数
selfDestruct();
// 重新初始化
init();
}
}; };
document.addEventListener(eventName, handler, { once: true }); if (typeof document.startViewTransition !== 'undefined') {
}); addListener(document, 'astro:page-load', pageLoadHandler);
// 页面卸载时清理
window.addEventListener('beforeunload', () => {
cleanup();
}, { once: true });
} }
// 初始化函数 if (typeof window.swup !== 'undefined') {
function init() { addListener(document, 'swup:contentReplaced', pageLoadHandler);
pageNavigationCount++; }
setupBackButton(); }
registerCleanup();
} }
// 监听页面转换事件 // 判断DOM是否已加载
function setupPageTransitionEvents() { if (document.readyState === "loading") {
// 确保事件处理程序唯一性的函数 document.addEventListener("DOMContentLoaded", init, { once: true });
function setupUniqueEvent(eventName, callback) {
const eventKey = `__back_button_event_${eventName.replace(/:/g, '_')}`;
// 移除可能存在的旧处理函数
if (window[eventKey]) {
document.removeEventListener(eventName, window[eventKey]);
}
// 保存新处理函数并注册
window[eventKey] = callback;
document.addEventListener(eventName, window[eventKey]);
}
// 页面转换后事件
const pageTransitionEvents = [
{ name: 'astro:after-swap', delay: 10 },
{ name: 'astro:page-load', delay: 10 },
{ name: 'swup:contentReplaced', delay: 10 }
];
// 设置每个页面转换事件
pageTransitionEvents.forEach(({ name, delay }) => {
setupUniqueEvent(name, () => {
cleanupButtonListeners(); // 立即清理按钮上的事件
// 延迟初始化确保DOM完全更新
setTimeout(() => {
cleanupButtonListeners(); // 再次清理,确保没有遗漏
init();
}, delay);
});
});
// 特别处理 swup:pageView 事件
setupUniqueEvent('swup:pageView', () => {
// 对于偶数次页面跳转,特别确保事件被正确重新绑定
if (pageNavigationCount % 2 === 0) {
setTimeout(() => {
const buttons = document.querySelectorAll('.back-button');
if (buttons.length > 0) {
cleanupButtonListeners();
setupBackButton();
}
}, 50);
}
});
}
// 设置页面转换事件监听
setupPageTransitionEvents();
// 在页面加载后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
init();
}, { once: true });
} else { } else {
setTimeout(() => {
init(); init();
}, 0);
} }
})(); })();
</script> </script>

View File

@ -332,8 +332,85 @@ const navSelectorClassName = "mr-4";
<script is:inline> <script is:inline>
// 自执行函数封装所有导航逻辑 // 导航逻辑 - 自销毁模式
(function initNavSelector() { (function() {
// 尽早检查导航元素是否存在,如果不存在则终止执行
const navSelector = document.querySelector('.nav-selector');
if (!navSelector) {
console.warn(`导航脚本未找到导航选择器元素,不执行导航脚本`);
return;
}
// 集中管理所有事件监听器
const allListeners = [];
// 单独保存清理事件的监听器引用
const cleanupListeners = [];
// 添加事件监听器并记录,方便后续统一清理
function addListener(element, eventType, handler, options) {
if (!element) {
console.warn(`导航脚本尝试为不存在的元素添加事件:`, eventType);
return null;
}
element.addEventListener(eventType, handler, options);
allListeners.push({ element, eventType, handler, options });
return handler;
}
// 统一的清理函数,执行完整清理并自销毁
function selfDestruct() {
// 1. 移除所有普通事件监听器
allListeners.forEach(({ element, eventType, handler, options }) => {
try {
element.removeEventListener(eventType, handler, options);
} catch (err) {
console.error(`导航脚本移除事件监听器出错:`, err);
}
});
// 清空监听器数组
allListeners.length = 0;
// 2. 最后移除清理事件监听器自身
cleanupListeners.forEach(({ element, eventType, handler, options }) => {
try {
element.removeEventListener(eventType, handler, options);
} catch (err) {
console.error(`导航脚本移除清理监听器出错:`, err);
}
});
// 清空清理监听器数组
cleanupListeners.length = 0;
}
// 注册清理事件,并保存引用
function registerCleanupEvents() {
// 创建一次性事件处理函数
const beforeSwapHandler = () => {
selfDestruct();
};
const beforeUnloadHandler = () => {
selfDestruct();
};
// 添加清理事件监听器并保存引用
document.addEventListener("astro:before-swap", beforeSwapHandler, { once: true });
window.addEventListener("beforeunload", beforeUnloadHandler, { once: true });
// 保存清理事件引用,用于完全销毁
cleanupListeners.push(
{ element: document, eventType: "astro:before-swap", handler: beforeSwapHandler, options: { once: true } },
{ element: window, eventType: "beforeunload", handler: beforeUnloadHandler, options: { once: true } }
);
}
// 创建一个共享的计算高亮位置的函数 // 创建一个共享的计算高亮位置的函数
function calculateHighlightPositions(options = {}) { function calculateHighlightPositions(options = {}) {
const { navSelector, immediate = false } = options; const { navSelector, immediate = false } = options;
@ -537,7 +614,6 @@ const navSelectorClassName = "mr-4";
// 在DOMContentLoaded前预先执行一次定位减少闪烁 // 在DOMContentLoaded前预先执行一次定位减少闪烁
(function prePositionHighlights() { (function prePositionHighlights() {
const navSelector = document.querySelector('.nav-selector');
if (!navSelector) return; if (!navSelector) return;
// 如果有活动项,尝试预先定位高亮 // 如果有活动项,尝试预先定位高亮
@ -553,35 +629,33 @@ const navSelectorClassName = "mr-4";
} }
})(); })();
// DOM加载完成后执行 // 注册清理事件
document.addEventListener('DOMContentLoaded', function() { registerCleanupEvents();
// 存储所有事件监听器,便于统一清理
const listeners = [];
// 添加事件监听器并记录,方便后续统一清理 // DOM加载完成后执行初始化
function addListener(element, eventType, handler, options) { function initNavigation() {
if (!element) return null; // 检查DOM是否已加载
element.addEventListener(eventType, handler, options); if (document.readyState === "loading") {
listeners.push({ element, eventType, handler, options }); document.addEventListener("DOMContentLoaded", setupNavigation, { once: true });
return handler; } else {
setupNavigation();
}
} }
// 清理函数 - 移除所有事件监听器 // 主要设置函数
function cleanup() { function setupNavigation() {
listeners.forEach(({ element, eventType, handler, options }) => {
try { // 设置桌面导航
element.removeEventListener(eventType, handler, options); setupNavSelector();
} catch (err) {
console.error('移除导航事件监听器出错:', err); // 设置移动端导航
} setupMobileNav();
});
listeners.length = 0;
} }
// 初始化导航选择器 // 初始化导航选择器
function setupNavSelector() { function setupNavSelector() {
// 获取DOM元素 // 获取DOM元素
const navSelector = document.querySelector('.nav-selector');
if (!navSelector) return; if (!navSelector) return;
const primaryHighlight = document.getElementById('nav-primary-highlight'); const primaryHighlight = document.getElementById('nav-primary-highlight');
@ -591,6 +665,10 @@ const navSelectorClassName = "mr-4";
const navToggles = document.querySelectorAll('.nav-group-toggle'); const navToggles = document.querySelectorAll('.nav-group-toggle');
const navSubItems = document.querySelectorAll('.nav-subitem'); const navSubItems = document.querySelectorAll('.nav-subitem');
if (!primaryHighlight || !secondaryHighlight) {
console.warn(`导航脚本未找到高亮元素,导航功能可能不完整`);
}
// 获取过渡动画持续时间 // 获取过渡动画持续时间
const transitionDuration = parseInt(navSelector.dataset.duration || 300); const transitionDuration = parseInt(navSelector.dataset.duration || 300);
const activeClass = "font-medium"; const activeClass = "font-medium";
@ -658,7 +736,9 @@ const navSelectorClassName = "mr-4";
// 兼容swup的导航方法 // 兼容swup的导航方法
function navigateTo(href) { function navigateTo(href) {
// 如果使用swup // 如果使用swup
if (window.swup) { const hasSwup = typeof window.swup !== 'undefined';
if (hasSwup) {
try { try {
// 正确使用swup的API // 正确使用swup的API
window.swup.navigate(href); window.swup.navigate(href);
@ -671,7 +751,7 @@ const navSelectorClassName = "mr-4";
}); });
return; return;
} catch (err2) { } catch (err2) {
console.error("Swup替代导航也出错:", err2); console.warn(`导航脚本Swup导航失败:`, err2);
} }
} }
} }
@ -1009,64 +1089,6 @@ const navSelectorClassName = "mr-4";
} }
} }
// 查找匹配当前路径的导航项
function findMatchingNavItem() {
const currentPath = window.location.pathname;
const normalizedPath = currentPath === '/' ? '/' :
currentPath.endsWith('/') ? currentPath.slice(0, -1) : currentPath;
// 首先检查子项
for (const subItem of navSubItems) {
const href = subItem.getAttribute('href');
if (href === normalizedPath) {
return {
type: 'subitem',
subitemId: subItem.dataset.subitemId,
parentId: subItem.dataset.parentId,
element: subItem
};
}
}
// 再检查主导航项
for (const item of navItems) {
const href = item.getAttribute('href');
if (href === normalizedPath) {
return {
type: 'item',
itemId: item.dataset.itemId,
element: item
};
}
}
// 没有完全匹配,尝试前缀匹配(用于文章详情等页面)
for (const subItem of navSubItems) {
const href = subItem.getAttribute('href');
if (normalizedPath.startsWith(href) && href !== '/') {
return {
type: 'subitem',
subitemId: subItem.dataset.subitemId,
parentId: subItem.dataset.parentId,
element: subItem
};
}
}
for (const item of navItems) {
const href = item.getAttribute('href');
if (normalizedPath.startsWith(href) && href !== '/') {
return {
type: 'item',
itemId: item.dataset.itemId,
element: item
};
}
}
return null;
}
// 初始化当前页面的激活状态 // 初始化当前页面的激活状态
function initActiveState() { function initActiveState() {
// 高亮背景已经在服务器端渲染时预设,现在需确保应用正确的文字颜色 // 高亮背景已经在服务器端渲染时预设,现在需确保应用正确的文字颜色
@ -1154,18 +1176,16 @@ const navSelectorClassName = "mr-4";
initActiveState(); initActiveState();
// 处理页面切换 // 处理页面切换
if (window.swup) { const hasSwup = typeof window.swup !== 'undefined';
if (hasSwup) {
// 页面内容替换后重新初始化 // 页面内容替换后重新初始化
addListener(document, 'swup:contentReplaced', () => { addListener(document, 'swup:contentReplaced', () => {
setTimeout(initActiveState, 50); setTimeout(initActiveState, 50);
}); });
// 页面内容替换前保存当前状态
addListener(document, 'swup:willReplaceContent', () => {
});
// 视图转换开始 // 视图转换开始
addListener(document, 'swup:animationInStart', () => { addListener(document, 'swup:animationInStart', () => {
// 这里可以添加动画开始时的处理逻辑
}); });
// 视图转换结束 // 视图转换结束
@ -1177,50 +1197,23 @@ const navSelectorClassName = "mr-4";
}); });
} }
// 添加Astro View Transitions事件监听
addListener(document, 'astro:page-load', () => {
setTimeout(initActiveState, 50);
updateHighlights(true);
});
// 窗口大小变化时重新计算高亮位置 // 窗口大小变化时重新计算高亮位置
addListener(window, 'resize', () => { addListener(window, 'resize', () => {
updateHighlights(true); updateHighlights(true);
}); });
// 在document上添加自定义方法方便外部调用 // 在document上添加自定义方法方便外部调用但使用弱引用避免内存泄漏
if (!document.resetNavigation) {
document.resetNavigation = function() { document.resetNavigation = function() {
initActiveState(); initActiveState();
}; };
// 动态检测swup实例
function checkSwup() {
if (!window.swup && typeof MutationObserver !== 'undefined') {
// 监听DOM变化检查swup实例是否被后续加载
const observer = new MutationObserver((mutations) => {
if (window.swup) {
// 添加swup事件监听
addListener(document, 'swup:contentReplaced', () => {
setTimeout(initActiveState, 50);
});
// 停止观察
observer.disconnect();
} }
});
// 开始观察文档
observer.observe(document.documentElement, {
childList: true,
subtree: true
});
// 5秒后自动停止观察避免长时间监听
setTimeout(() => {
observer.disconnect();
}, 5000);
}
}
// 检测swup实例
checkSwup();
// 返回清理函数
return cleanup;
} }
// 初始化移动端菜单和搜索功能 // 初始化移动端菜单和搜索功能
@ -1232,8 +1225,14 @@ const navSelectorClassName = "mr-4";
const menuOpenIcon = document.getElementById('menu-open-icon'); const menuOpenIcon = document.getElementById('menu-open-icon');
const menuCloseIcon = document.getElementById('menu-close-icon'); const menuCloseIcon = document.getElementById('menu-close-icon');
if (!mobileMenuButton || !mobileMenu) {
console.warn(`导航脚本未找到移动端菜单组件`);
return;
}
// 更新移动端菜单高亮状态的函数 // 更新移动端菜单高亮状态的函数
function updateMobileMenuHighlight() { function updateMobileMenuHighlight() {
// 获取当前路径 // 获取当前路径
const currentPath = window.location.pathname; const currentPath = window.location.pathname;
const normalizedPath = currentPath === '/' ? '/' : const normalizedPath = currentPath === '/' ? '/' :
@ -1257,7 +1256,7 @@ const navSelectorClassName = "mr-4";
const parentId = submenu.getAttribute('data-parent-id'); const parentId = submenu.getAttribute('data-parent-id');
const toggle = document.querySelector(`[data-mobile-menu-toggle="${parentId}"]`); const toggle = document.querySelector(`[data-mobile-menu-toggle="${parentId}"]`);
if (toggle && submenu.classList.contains('hidden')) { if (toggle && submenu.classList.contains('hidden')) {
// 模拟点击父菜单,展开子菜单 // 展开子菜单
toggleSubmenu(parentId, true); toggleSubmenu(parentId, true);
} }
} }
@ -1303,6 +1302,11 @@ const navSelectorClassName = "mr-4";
}); });
} }
// 为所有子菜单切换按钮添加点击事件
function setupMobileSubmenuToggles() {
// 关闭所有子菜单(默认状态)
closeAllSubmenus();
// 为所有子菜单切换按钮添加点击事件 // 为所有子菜单切换按钮添加点击事件
const submenuToggles = document.querySelectorAll('[data-mobile-menu-toggle]'); const submenuToggles = document.querySelectorAll('[data-mobile-menu-toggle]');
submenuToggles.forEach(toggle => { submenuToggles.forEach(toggle => {
@ -1313,6 +1317,7 @@ const navSelectorClassName = "mr-4";
} }
}); });
}); });
}
// 关闭移动端菜单 // 关闭移动端菜单
function closeMobileMenu() { function closeMobileMenu() {
@ -1345,9 +1350,7 @@ const navSelectorClassName = "mr-4";
} }
} }
// 初始调用一次更新高亮状态(会自动展开包含当前页面的子菜单) // 移动端菜单按钮点击事件
updateMobileMenuHighlight();
if (mobileMenuButton && mobileMenu) { if (mobileMenuButton && mobileMenu) {
addListener(mobileMenuButton, 'click', () => { addListener(mobileMenuButton, 'click', () => {
// 切换菜单显示状态 // 切换菜单显示状态
@ -1373,6 +1376,7 @@ const navSelectorClassName = "mr-4";
}); });
} }
// 移动端搜索按钮点击事件
if (mobileSearchButton && mobileSearch) { if (mobileSearchButton && mobileSearch) {
addListener(mobileSearchButton, 'click', () => { addListener(mobileSearchButton, 'click', () => {
// 切换搜索面板显示状态 // 切换搜索面板显示状态
@ -1394,12 +1398,16 @@ const navSelectorClassName = "mr-4";
mobileMenuLinks.forEach(link => { mobileMenuLinks.forEach(link => {
addListener(link, 'click', (e) => { addListener(link, 'click', (e) => {
// 如果使用客户端路由如swup或Astro View Transitions阻止默认行为 // 如果使用客户端路由如swup或Astro View Transitions阻止默认行为
if (window.swup || document.startViewTransition) { const hasSwup = typeof window.swup !== 'undefined';
const hasViewTransitions = typeof document.startViewTransition !== 'undefined';
if (hasSwup || hasViewTransitions) {
e.preventDefault(); e.preventDefault();
// 获取链接地址 // 获取链接地址
const href = link.getAttribute('href'); const href = link.getAttribute('href');
if (href) { if (!href) return;
// 先关闭菜单 // 先关闭菜单
closeMobileMenu(); closeMobileMenu();
closeMobileSearch(); closeMobileSearch();
@ -1407,7 +1415,7 @@ const navSelectorClassName = "mr-4";
// 延迟导航,确保菜单关闭动画完成 // 延迟导航,确保菜单关闭动画完成
setTimeout(() => { setTimeout(() => {
// 使用适当的导航方法 // 使用适当的导航方法
if (window.swup) { if (hasSwup) {
try { try {
window.swup.navigate(href); window.swup.navigate(href);
} catch (err) { } catch (err) {
@ -1417,7 +1425,7 @@ const navSelectorClassName = "mr-4";
window.location.href = href; window.location.href = href;
} }
} }
} else if (document.startViewTransition) { } else if (hasViewTransitions) {
document.startViewTransition(() => { document.startViewTransition(() => {
window.location.href = href; window.location.href = href;
}); });
@ -1425,7 +1433,6 @@ const navSelectorClassName = "mr-4";
window.location.href = href; window.location.href = href;
} }
}, 50); }, 50);
}
} else { } else {
// 普通链接导航,浏览器会自动处理跳转 // 普通链接导航,浏览器会自动处理跳转
// 但仍然需要关闭菜单 // 但仍然需要关闭菜单
@ -1435,86 +1442,28 @@ const navSelectorClassName = "mr-4";
}); });
}); });
// 处理主题切换容器点击 // 为Astro View Transitions添加事件处理
const themeToggleContainer = document.getElementById('theme-toggle-container');
if (themeToggleContainer) {
addListener(themeToggleContainer, 'click', (e) => {
// 触发其中的ThemeToggle组件点击
const themeToggle = themeToggleContainer.querySelector('button');
if (themeToggle) {
themeToggle.click();
}
});
}
// 处理页面切换,确保移动端菜单高亮状态更新
if (window.swup) {
// swup页面内容替换后
addListener(document, 'swup:contentReplaced', () => {
setTimeout(() => {
// 更新移动端菜单项的事件监听器因为DOM已经更新
setupMobileSubmenuToggles();
// 更新高亮状态
updateMobileMenuHighlight();
// 确保菜单关闭
closeMobileMenu();
closeMobileSearch();
}, 50);
});
// swup动画结束后
addListener(document, 'swup:animationInDone', () => {
updateMobileMenuHighlight();
});
}
// 如果使用Astro的View Transitions
addListener(document, 'astro:page-load', () => { addListener(document, 'astro:page-load', () => {
// 更新移动端菜单项的事件监听器因为DOM已经更新
setupMobileSubmenuToggles(); setupMobileSubmenuToggles();
// 更新高亮状态
updateMobileMenuHighlight(); updateMobileMenuHighlight();
// 确保菜单关闭
closeMobileMenu(); closeMobileMenu();
closeMobileSearch(); closeMobileSearch();
}); });
// 普通页面加载事件 // 监听地址栏变化
addListener(window, 'popstate', () => { addListener(window, 'popstate', () => {
setTimeout(() => { setTimeout(() => {
// 更新移动端菜单项的事件监听器因为DOM可能已经更新
setupMobileSubmenuToggles();
// 更新高亮状态
updateMobileMenuHighlight(); updateMobileMenuHighlight();
// 确保菜单关闭
closeMobileMenu(); closeMobileMenu();
closeMobileSearch(); closeMobileSearch();
}, 50); }, 50);
}); });
// 辅助函数:为移动端子菜单添加切换事件(用于页面切换后重新绑定) // 初始调用一次设置子菜单切换按钮和更新高亮状态
function setupMobileSubmenuToggles() { setupMobileSubmenuToggles();
// 关闭所有子菜单(默认状态) updateMobileMenuHighlight();
closeAllSubmenus();
// 重新添加事件监听器 // 在document上添加自定义方法方便外部调用可选
const submenuToggles = document.querySelectorAll('[data-mobile-menu-toggle]');
submenuToggles.forEach(toggle => {
// 移除可能存在的旧监听器(通过克隆和替换元素)
const newToggle = toggle.cloneNode(true);
toggle.parentNode.replaceChild(newToggle, toggle);
// 添加新的事件监听器
addListener(newToggle, 'click', (e) => {
const parentId = newToggle.getAttribute('data-mobile-menu-toggle');
if (parentId) {
toggleSubmenu(parentId);
}
});
});
}
// 暴露更新和关闭函数到全局,方便其他地方调用
document.updateMobileMenuHighlight = updateMobileMenuHighlight; document.updateMobileMenuHighlight = updateMobileMenuHighlight;
document.closeMobileMenu = closeMobileMenu; document.closeMobileMenu = closeMobileMenu;
document.closeMobileSearch = closeMobileSearch; document.closeMobileSearch = closeMobileSearch;
@ -1523,13 +1472,8 @@ const navSelectorClassName = "mr-4";
return updateMobileMenuHighlight; return updateMobileMenuHighlight;
} }
// 执行初始化 // 开始初始化
const cleanupNav = setupNavSelector(); initNavigation();
const updateMobileHighlight = setupMobileNav();
// 页面卸载时清理
window.addEventListener('beforeunload', cleanupNav);
});
})(); })();
</script> </script>

View File

@ -204,7 +204,6 @@ const Search: React.FC<SearchProps> = ({
// 如果是取消的请求,不显示错误 // 如果是取消的请求,不显示错误
if (err instanceof Error && (err.name === 'AbortError' || err.message.includes('aborted'))) { if (err instanceof Error && (err.name === 'AbortError' || err.message.includes('aborted'))) {
console.log('索引加载请求被取消:', err.message);
return; return;
} }
@ -239,7 +238,7 @@ const Search: React.FC<SearchProps> = ({
} }
}; };
window.addEventListener('resize', handleResize, { passive: true }); window.addEventListener('resize', handleResize, { passive: false });
return () => { return () => {
window.removeEventListener('resize', handleResize); window.removeEventListener('resize', handleResize);
}; };
@ -248,18 +247,35 @@ const Search: React.FC<SearchProps> = ({
// 处理点击外部关闭搜索结果和建议 // 处理点击外部关闭搜索结果和建议
useEffect(() => { useEffect(() => {
const handleClickOutside = (event: MouseEvent) => { const handleClickOutside = (event: MouseEvent) => {
// 获取事件目标元素
const target = event.target as Node;
// 检查是否点击了清除按钮、Tab按钮或其子元素
const clearButtonEl = document.querySelector('.clear-search-button');
const tabButtonEl = document.querySelector('.tab-completion-button');
const isClickOnClearButton = clearButtonEl && (clearButtonEl === target || clearButtonEl.contains(target));
const isClickOnTabButton = tabButtonEl && (tabButtonEl === target || tabButtonEl.contains(target));
// 如果点击了清除按钮或Tab按钮不做任何操作
if (isClickOnClearButton || isClickOnTabButton) {
return;
}
// 原有的逻辑:点击搜索框和结果区域之外时关闭
if ( if (
searchResultsRef.current && searchResultsRef.current &&
!searchResultsRef.current.contains(event.target as Node) && !searchResultsRef.current.contains(target) &&
searchInputRef.current && searchInputRef.current &&
!searchInputRef.current.contains(event.target as Node) !searchInputRef.current.contains(target)
) { ) {
// 当点击搜索框和结果区域之外时,才隐藏结果
setShowResults(false); setShowResults(false);
setInlineSuggestion(prev => ({ ...prev, visible: false })); // 也隐藏内联建议 setInlineSuggestion(prev => ({ ...prev, visible: false })); // 也隐藏内联建议
} }
}; };
document.addEventListener("mousedown", handleClickOutside, { passive: true }); document.addEventListener("mousedown", handleClickOutside, { passive: false });
return () => { return () => {
document.removeEventListener("mousedown", handleClickOutside); document.removeEventListener("mousedown", handleClickOutside);
}; };
@ -299,6 +315,7 @@ const Search: React.FC<SearchProps> = ({
setSuggestions([]); setSuggestions([]);
setInlineSuggestion(prev => ({ ...prev, visible: false })); setInlineSuggestion(prev => ({ ...prev, visible: false }));
setSelectedSuggestionIndex(0); // 重置选中索引 setSelectedSuggestionIndex(0); // 重置选中索引
console.log("[建议] 没有符合条件的查询或模块未加载");
return; return;
} }
@ -325,6 +342,7 @@ const Search: React.FC<SearchProps> = ({
const searchResult = JSON.parse(result) as SearchResult; const searchResult = JSON.parse(result) as SearchResult;
// 检查组件是否仍然挂载 // 检查组件是否仍然挂载
if (!isMountedRef.current) return; if (!isMountedRef.current) return;
@ -347,6 +365,7 @@ const Search: React.FC<SearchProps> = ({
const firstSuggestion = searchResult.suggestions[0]; const firstSuggestion = searchResult.suggestions[0];
if (firstSuggestion) { if (firstSuggestion) {
setInlineSuggestion(prev => ({ setInlineSuggestion(prev => ({
...prev, ...prev,
text: firstSuggestion.text, text: firstSuggestion.text,
@ -363,7 +382,7 @@ const Search: React.FC<SearchProps> = ({
// 检查组件是否仍然挂载 // 检查组件是否仍然挂载
if (!isMountedRef.current) return; if (!isMountedRef.current) return;
console.error("获取内联建议失败:", err); console.error("[建议错误]", err);
setInlineSuggestion(prev => ({ ...prev, visible: false })); setInlineSuggestion(prev => ({ ...prev, visible: false }));
setSelectedSuggestionIndex(0); // 重置选中索引 setSelectedSuggestionIndex(0); // 重置选中索引
} }
@ -432,9 +451,11 @@ const Search: React.FC<SearchProps> = ({
// 修改处理键盘导航的函数,增加上下箭头键切换建议 // 修改处理键盘导航的函数,增加上下箭头键切换建议
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
// Tab键处理内联建议补全 // Tab键处理内联建议补全
if (e.key === "Tab" && inlineSuggestion.visible && inlineSuggestion.text) { if (e.key === "Tab" && inlineSuggestion.visible && inlineSuggestion.text) {
e.preventDefault(); // 阻止默认的Tab行为 e.preventDefault(); // 阻止默认的Tab行为
e.stopPropagation(); // 防止事件冒泡
completeInlineSuggestion(); completeInlineSuggestion();
return; return;
} }
@ -532,50 +553,13 @@ const Search: React.FC<SearchProps> = ({
} }
}; };
document.addEventListener('selectionchange', handleSelectionChange, { passive: true }); // 使用非被动模式确保在某些上下文中可以调用preventDefault
document.addEventListener('selectionchange', handleSelectionChange, { passive: false });
return () => { return () => {
document.removeEventListener('selectionchange', handleSelectionChange); document.removeEventListener('selectionchange', handleSelectionChange);
}; };
}, [updateCaretPosition]); }, [updateCaretPosition]);
// 自动补全内联建议
const completeInlineSuggestion = () => {
if (inlineSuggestion.visible && inlineSuggestion.text) {
// 保存建议文本
const suggestionText = inlineSuggestion.text;
const isCorrection = inlineSuggestion.type === 'correction';
// 完全清除内联建议状态
setInlineSuggestion({
text: "",
visible: false,
caretPosition: 0,
selection: {start: 0, end: 0},
type: 'completion',
matchedText: "",
suggestionText: ""
});
// 设置完整的建议作为新的查询
setQuery(suggestionText);
// 将光标移到末尾并执行搜索
setTimeout(() => {
if (searchInputRef.current) {
searchInputRef.current.focus();
searchInputRef.current.setSelectionRange(
suggestionText.length,
suggestionText.length
);
}
// 直接使用suggestionText执行搜索而不是依赖query状态
// 因为React状态更新是异步的此时query可能还未更新
performSearch(suggestionText, false);
}, 0);
}
};
// 执行搜索 // 执行搜索
const performSearch = async (searchQuery: string, isLoadMore: boolean = false) => { const performSearch = async (searchQuery: string, isLoadMore: boolean = false) => {
if (!wasmModule || !isIndexLoaded || !indexData || !searchQuery.trim()) { if (!wasmModule || !isIndexLoaded || !indexData || !searchQuery.trim()) {
@ -667,6 +651,46 @@ const Search: React.FC<SearchProps> = ({
} }
}; };
// 自动补全内联建议 - 不使用useCallback避免循环依赖
const completeInlineSuggestion = () => {
if (inlineSuggestion.visible && inlineSuggestion.text) {
// 保存建议文本
const textToComplete = inlineSuggestion.text;
// 直接更新DOM和状态
if (searchInputRef.current) {
// 立即更新输入框值
searchInputRef.current.value = textToComplete;
}
// 清除内联建议状态
setInlineSuggestion({
text: "",
visible: false,
caretPosition: 0,
selection: {start: 0, end: 0},
type: 'completion',
matchedText: "",
suggestionText: ""
});
// 更新React状态
setQuery(textToComplete);
// 立即执行搜索
performSearch(textToComplete, false);
// 聚焦输入框并设置光标位置
if (searchInputRef.current) {
searchInputRef.current.focus();
searchInputRef.current.setSelectionRange(
textToComplete.length,
textToComplete.length
);
}
}
};
// 高亮显示匹配文本 - 不再处理高亮,完全依赖后端 // 高亮显示匹配文本 - 不再处理高亮,完全依赖后端
const processHighlightedContent = (content: string) => { const processHighlightedContent = (content: string) => {
// 检查内容是否为空 // 检查内容是否为空
@ -790,6 +814,59 @@ const Search: React.FC<SearchProps> = ({
suggestionEl.style.fontWeight = inputStyle.fontWeight; suggestionEl.style.fontWeight = inputStyle.fontWeight;
suggestionEl.style.letterSpacing = inputStyle.letterSpacing; suggestionEl.style.letterSpacing = inputStyle.letterSpacing;
suggestionEl.style.lineHeight = inputStyle.lineHeight; suggestionEl.style.lineHeight = inputStyle.lineHeight;
// 计算并调整可用空间
if (inlineSuggestion.type === 'correction') {
// 获取输入框宽度
const inputWidth = searchInputRef.current!.offsetWidth;
// 估算查询文本宽度 (使用更精确的字体宽度估算方法)
// 创建一个临时元素用于测量实际宽度
const tempMeasureEl = document.createElement('span');
tempMeasureEl.style.visibility = 'hidden';
tempMeasureEl.style.position = 'absolute';
tempMeasureEl.style.whiteSpace = 'pre';
tempMeasureEl.style.fontSize = inputStyle.fontSize;
tempMeasureEl.style.fontFamily = inputStyle.fontFamily;
tempMeasureEl.style.fontWeight = inputStyle.fontWeight;
tempMeasureEl.style.letterSpacing = inputStyle.letterSpacing;
tempMeasureEl.innerText = query;
document.body.appendChild(tempMeasureEl);
const queryTextWidth = tempMeasureEl.offsetWidth;
document.body.removeChild(tempMeasureEl);
// 计算右侧边距 (确保TAB按钮和清除按钮有足够空间)
// 根据屏幕尺寸调整右侧边距
let rightMargin = 90; // 默认桌面环境
// 根据窗口宽度调整边距(响应式设计)
if (window.innerWidth < 640) { // 小屏幕设备
rightMargin = 100; // 移动设备上按钮占据更多相对空间
} else if (window.innerWidth < 768) { // 中等屏幕设备
rightMargin = 95;
}
// 计算建议可用最大宽度
// 根据屏幕尺寸调整最大宽度百分比
let maxWidthPercentage = 0.8; // 默认最大宽度百分比
if (window.innerWidth < 640) {
maxWidthPercentage = 0.7; // 在小屏幕上减少最大宽度百分比
}
const maxAllowedWidth = Math.floor(inputWidth * maxWidthPercentage);
// 计算最终的可用宽度
const availableWidth = Math.min(
maxAllowedWidth,
Math.max(inputWidth - queryTextWidth - rightMargin, 80) // 最小宽度降低到80px以适应更小的设备
);
// 设置最大宽度
const suggestionTextContainer = suggestionEl.querySelector('div > div:nth-child(2) > span');
if (suggestionTextContainer) {
(suggestionTextContainer as HTMLElement).style.maxWidth = `${availableWidth}px`;
}
}
}; };
updateSuggestionStyles(); updateSuggestionStyles();
@ -802,14 +879,46 @@ const Search: React.FC<SearchProps> = ({
resizeObserver.disconnect(); resizeObserver.disconnect();
}; };
} }
}, [inlineSuggestion.visible, query]); }, [inlineSuggestion.visible, query, inlineSuggestion.type]);
// 处理Tab键盘事件 - 简化逻辑,改进处理方式
useEffect(() => {
// 创建键盘事件处理函数
const handleTabKey = (e: KeyboardEvent) => {
const isFocused = document.activeElement === searchInputRef.current;
const hasVisibleSuggestion = inlineSuggestion.visible && inlineSuggestion.text;
if (e.key === 'Tab' && isFocused && hasVisibleSuggestion) {
e.preventDefault();
e.stopPropagation();
completeInlineSuggestion();
}
};
// 添加键盘事件监听器,确保使用非被动模式
document.addEventListener('keydown', handleTabKey, { passive: false, capture: true });
return () => {
document.removeEventListener('keydown', handleTabKey, { capture: true });
};
}, []);
// 清除搜索 // 清除搜索
const clearSearch = () => { const clearSearch = () => {
// 清除查询和结果,但保持搜索框的可见状态
setQuery(""); setQuery("");
if (searchInputRef.current) {
searchInputRef.current.value = "";
}
// 清除搜索结果
setSearchResults(null); setSearchResults(null);
setAllItems([]); setAllItems([]);
setSuggestions([]); setSuggestions([]);
// 清除内联建议
setInlineSuggestion({ setInlineSuggestion({
text: "", text: "",
visible: false, visible: false,
@ -819,10 +928,13 @@ const Search: React.FC<SearchProps> = ({
matchedText: "", matchedText: "",
suggestionText: "" suggestionText: ""
}); });
setShowResults(false);
// 保持结果区域可见,但无内容
setShowResults(true);
setCurrentPage(1); setCurrentPage(1);
setHasMoreResults(true); setHasMoreResults(true);
// 确保输入框保持焦点
if (searchInputRef.current) { if (searchInputRef.current) {
searchInputRef.current.focus(); searchInputRef.current.focus();
} }
@ -844,8 +956,8 @@ const Search: React.FC<SearchProps> = ({
} }
}; };
window.addEventListener('focus', checkFocus, { passive: true, capture: true }); window.addEventListener('focus', checkFocus, { passive: false, capture: true });
window.addEventListener('blur', checkFocus, { passive: true, capture: true }); window.addEventListener('blur', checkFocus, { passive: false, capture: true });
return () => { return () => {
window.removeEventListener('focus', checkFocus, { capture: true }); window.removeEventListener('focus', checkFocus, { capture: true });
@ -853,22 +965,6 @@ const Search: React.FC<SearchProps> = ({
}; };
}, [isFocused]); }, [isFocused]);
// 处理Tab键盘事件
useEffect(() => {
const handleTabKey = (e: KeyboardEvent) => {
if (e.key === 'Tab' && document.activeElement === searchInputRef.current && inlineSuggestion.visible) {
e.preventDefault();
completeInlineSuggestion();
}
};
document.addEventListener('keydown', handleTabKey);
return () => {
document.removeEventListener('keydown', handleTabKey);
};
}, [inlineSuggestion.visible, completeInlineSuggestion]);
// 获取当前placeholder文本 // 获取当前placeholder文本
const getCurrentPlaceholder = () => { const getCurrentPlaceholder = () => {
if (isLoadingIndex) { if (isLoadingIndex) {
@ -1090,7 +1186,7 @@ const Search: React.FC<SearchProps> = ({
onSelect={updateCaretPosition} onSelect={updateCaretPosition}
onFocus={handleInputFocus} onFocus={handleInputFocus}
placeholder={getCurrentPlaceholder()} placeholder={getCurrentPlaceholder()}
className="w-full py-1.5 md:py-1.5 lg:py-2.5 pl-8 md:pl-8 lg:pl-10 pr-8 md:pr-8 lg:pr-10 text-sm md:text-sm lg:text-base bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg md:rounded-lg lg:rounded-xl text-gray-800 dark:text-gray-200 focus:outline-none focus:ring-1 focus:ring-primary-500 focus:border-transparent focus:shadow-md transition-all duration-200 relative z-10" className="w-full py-2.5 md:py-1.5 lg:py-2.5 pl-10 md:pl-8 lg:pl-10 pr-10 md:pr-8 lg:pr-10 text-base md:text-sm lg:text-base bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg md:rounded-lg lg:rounded-xl text-gray-800 dark:text-gray-200 focus:outline-none focus:ring-1 focus:ring-primary-500 focus:border-transparent focus:shadow-md transition-all duration-200 relative z-10"
disabled={isLoadingIndex || !isIndexLoaded} disabled={isLoadingIndex || !isIndexLoaded}
style={{ backgroundColor: 'transparent' }} // 确保背景是透明的,这样可以看到下面的建议 style={{ backgroundColor: 'transparent' }} // 确保背景是透明的,这样可以看到下面的建议
/> />
@ -1106,25 +1202,37 @@ const Search: React.FC<SearchProps> = ({
}} }}
> >
{/* 修改显示方式,确保与输入文本对齐,同时支持响应式布局 */} {/* 修改显示方式,确保与输入文本对齐,同时支持响应式布局 */}
<div className="flex w-full px-8 md:px-8 lg:px-10 overflow-hidden"> {/* 使用与输入框相同的水平内边距,添加溢出隐藏 */} <div className="flex w-full px-10 md:px-8 lg:px-10 overflow-hidden"> {/* 使用与输入框相同的水平内边距,添加溢出隐藏 */}
{/* 纠正建议和补全建议都显示在已输入内容的右侧 */} {/* 纠正建议和补全建议都显示在已输入内容的右侧 */}
<> <>
{/* 创建与输入文本宽度完全相等的不可见占位 */} {/* 创建与输入文本宽度完全相等的不可见占位 */}
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<span className="invisible whitespace-pre text-sm md:text-sm lg:text-base">{query}</span> <span className="invisible whitespace-pre text-base md:text-sm lg:text-base">{query}</span>
</div> </div>
{/* 显示建议的剩余部分 */} {/* 显示建议的剩余部分 */}
<div className="flex-shrink-0 max-w-[70%]"> <div className={`flex-shrink-0 ${
<span // 根据建议类型调整最大宽度
className={`whitespace-pre text-sm md:text-sm lg:text-base truncate block ${
inlineSuggestion.type === 'correction' inlineSuggestion.type === 'correction'
? 'text-amber-500/80 dark:text-amber-400/80 ml-1' ? 'max-w-[calc(100%-1.25rem)]' // 纠正建议给予更多空间,但仍然保留一些边距
: 'max-w-[80%]' // 补全建议使用固定比例
}`}>
<span
className={`whitespace-pre text-base md:text-sm lg:text-base ${
// 对纠正建议使用ellipsis确保文本不会溢出
inlineSuggestion.type === 'correction'
? 'text-amber-500/80 dark:text-amber-400/80 ml-1 block truncate'
: 'text-gray-400/70 dark:text-gray-500/70' : 'text-gray-400/70 dark:text-gray-500/70'
}`} }`}
style={{ style={{
fontWeight: 'bold', fontWeight: inlineSuggestion.type === 'correction' ? '600' : 'bold',
textDecoration: inlineSuggestion.type === 'correction' ? 'underline dotted 1px' : 'none',
textUnderlineOffset: '2px',
marginLeft: inlineSuggestion.type === 'completion' ? '0px' : undefined, marginLeft: inlineSuggestion.type === 'completion' ? '0px' : undefined,
// 确保溢出时有优雅的省略效果
overflow: 'hidden',
textOverflow: 'ellipsis',
}} }}
title={inlineSuggestion.type === 'correction' ? inlineSuggestion.text : undefined} // 在纠正模式下添加完整文本提示
> >
{inlineSuggestion.suggestionText} {inlineSuggestion.suggestionText}
</span> </span>
@ -1135,43 +1243,89 @@ const Search: React.FC<SearchProps> = ({
)} )}
{/* 搜索图标 */} {/* 搜索图标 */}
<div className="absolute left-2.5 md:left-2.5 left-3.5 top-1/2 transform -translate-y-1/2 z-20"> <div className="absolute left-3.5 md:left-2.5 lg:left-3.5 top-1/2 transform -translate-y-1/2 z-20">
<svg className="h-3.5 w-3.5 md:h-3.5 md:w-3.5 h-4.5 w-4.5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg className="h-5 w-5 md:h-3.5 md:w-3.5 lg:h-4.5 lg:w-4.5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg> </svg>
</div> </div>
{/* 加载指示器或清除按钮 */} {/* 加载指示器或清除按钮 */}
<div className="absolute right-2.5 md:right-2.5 right-3.5 top-1/2 transform -translate-y-1/2 z-20 flex items-center"> <div className="absolute right-3.5 md:right-2.5 lg:right-3.5 top-1/2 transform -translate-y-1/2 z-20 flex items-center">
{isLoading ? ( {isLoading ? (
<div className="animate-spin rounded-full h-3.5 w-3.5 md:h-3.5 md:w-3.5 h-4.5 w-4.5 border-2 border-primary-600 border-t-transparent"></div> <div className="animate-spin rounded-full h-5 w-5 md:h-3.5 md:w-3.5 lg:h-4.5 lg:w-4.5 border-2 border-primary-600 border-t-transparent"></div>
) : query ? ( ) : query ? (
<> <>
<button <button
type="button" type="button"
onClick={clearSearch} className="text-gray-400 hover:text-primary-500 dark:hover:text-primary-400 focus:outline-none active:text-primary-600 dark:active:text-primary-300 flex items-center justify-center p-2 -m-1 clear-search-button"
className="text-gray-400 hover:text-primary-500 dark:hover:text-primary-400 focus:outline-none active:text-primary-600 dark:active:text-primary-300 flex items-center justify-center p-1 -m-1"
title="清除搜索" title="清除搜索"
style={{ touchAction: 'none' }}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation(); // 阻止事件冒泡到document
// 只清除文本,始终不关闭搜索框
clearSearch();
}}
onTouchStart={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onTouchEnd={(e) => {
e.preventDefault();
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation(); // 阻止事件冒泡到document
clearSearch();
}}
> >
<svg className="h-3.5 w-3.5 md:h-3.5 md:w-3.5 h-4.5 w-4.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg className="h-5 w-5 md:h-3.5 md:w-3.5 lg:h-4.5 lg:w-4.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg> </svg>
</button> </button>
{inlineSuggestion.visible && inlineSuggestion.text && ( {inlineSuggestion.visible && inlineSuggestion.text && (
<div <div
className="text-gray-400 hover:text-primary-500 dark:hover:text-primary-400 active:text-primary-600 dark:active:text-primary-300 flex items-center justify-center cursor-pointer p-1 ml-1" className={`text-gray-400 hover:text-primary-500 dark:hover:text-primary-400 active:text-primary-600 dark:active:text-primary-300 flex items-center justify-center cursor-pointer p-1 ml-1 tab-completion-button ${
title="按Tab键补全" inlineSuggestion.type === 'correction' ? 'animate-pulse' : ''
onClick={completeInlineSuggestion} }`}
title={inlineSuggestion.type === 'correction' ? "按Tab键接受纠正" : "按Tab键补全"}
onClick={(e) => {
// 阻止冒泡和默认行为
e.preventDefault();
e.stopPropagation();
// 直接执行补全操作,不再使用延迟和多次更新
completeInlineSuggestion();
}}
onMouseDown={(e) => {
// 阻止失去焦点
e.preventDefault();
e.stopPropagation();
}}
onTouchStart={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onTouchEnd={(e) => {
e.preventDefault();
e.stopPropagation();
completeInlineSuggestion();
}}
role="button" role="button"
tabIndex={0} tabIndex={0}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') { if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
completeInlineSuggestion(); completeInlineSuggestion();
} }
}} }}
style={{ touchAction: 'none' }}
> >
<div className="border border-current rounded px-1 py-px text-[8px] md:text-[8px] leading-none font-semibold flex items-center justify-center"> <div className={`border ${
inlineSuggestion.type === 'correction'
? 'border-amber-500/80 text-amber-500/90 dark:border-amber-400/80 dark:text-amber-400/90'
: 'border-current'
} rounded px-1 py-px text-[10px] md:text-[8px] lg:text-[8px] leading-none font-semibold flex items-center justify-center`}>
TAB TAB
</div> </div>
</div> </div>
@ -1179,11 +1333,11 @@ const Search: React.FC<SearchProps> = ({
</> </>
) : error ? ( ) : error ? (
<div className="flex items-center"> <div className="flex items-center">
<div className="rounded-full h-2 w-2 md:h-2 md:w-2 h-3 w-3 bg-red-500 shadow-sm shadow-red-500/50"></div> <div className="rounded-full h-3 w-3 md:h-2 md:w-2 lg:h-3 lg:w-3 bg-red-500 shadow-sm shadow-red-500/50"></div>
</div> </div>
) : isLoadingIndex ? ( ) : isLoadingIndex ? (
<div className="flex items-center"> <div className="flex items-center">
<div className="animate-pulse rounded-full h-2 w-2 md:h-2 md:w-2 h-3 w-3 bg-yellow-500 shadow-sm shadow-yellow-500/50"></div> <div className="animate-pulse rounded-full h-3 w-3 md:h-2 md:w-2 lg:h-3 lg:w-3 bg-yellow-500 shadow-sm shadow-yellow-500/50"></div>
</div> </div>
) : null} ) : null}
</div> </div>

File diff suppressed because it is too large Load Diff

View File

@ -473,46 +473,129 @@ const tableOfContents = generateTableOfContents(headings);
<!-- 文章页面脚本 --> <!-- 文章页面脚本 -->
<script is:inline> <script is:inline>
// 文章页面交互脚本 // 文章页面交互脚本 - 自销毁模式
(function () { (function() {
// 存储事件监听器,便于统一清理 // 如果不是文章页面,立即退出,不执行任何代码
const listeners = []; if (!document.querySelector("article")) {
return;
}
const scriptInstanceId = Date.now();
console.log(`[文章脚本:${scriptInstanceId}] 检测到文章页面,开始初始化`);
// 集中管理所有事件监听器
const allListeners = [];
// 为特殊清理任务准备的数组
const customCleanupTasks = [];
// 单独保存清理事件的监听器引用
const cleanupListeners = [];
// 添加事件监听器并记录,方便后续统一清理 // 添加事件监听器并记录,方便后续统一清理
function addListener(element, eventType, handler, options) { function addListener(element, eventType, handler, options) {
if (!element) return null; if (!element) {
console.warn(`[文章脚本:${scriptInstanceId}] 尝试为不存在的元素添加事件:`, eventType);
return null;
}
console.log(`[文章脚本:${scriptInstanceId}] 添加事件监听器: ${eventType} 到`, element.tagName || "Window/Document");
element.addEventListener(eventType, handler, options); element.addEventListener(eventType, handler, options);
listeners.push({ element, eventType, handler, options }); allListeners.push({ element, eventType, handler, options });
return handler; return handler;
} }
// 清理函数,移除所有事件监听器 // 统一的清理函数,执行完整清理并自销毁
function cleanup() { function selfDestruct() {
listeners.forEach(({ element, eventType, handler, options }) => { console.log(`[文章脚本:${scriptInstanceId}] 执行自销毁流程`);
// 1. 先移除普通事件监听器
console.log(`[文章脚本:${scriptInstanceId}] 移除常规监听器,数量:`, allListeners.length);
allListeners.forEach(({ element, eventType, handler, options }) => {
try { try {
console.log(`[文章脚本:${scriptInstanceId}] 移除事件监听器: ${eventType} 从`, element.tagName || "Window/Document");
element.removeEventListener(eventType, handler, options); element.removeEventListener(eventType, handler, options);
} catch (err) { } catch (err) {
// 忽略错误 console.error(`[文章脚本:${scriptInstanceId}] 移除事件监听器出错:`, err);
} }
}); });
listeners.length = 0; // 清空监听器数组
allListeners.length = 0;
// 2. 执行特殊清理任务
console.log(`[文章脚本:${scriptInstanceId}] 执行特殊清理任务,数量:`, customCleanupTasks.length);
customCleanupTasks.forEach(task => {
try {
task();
} catch (err) {
console.error(`[文章脚本:${scriptInstanceId}] 执行特殊清理任务出错:`, err);
}
});
// 清空特殊任务数组
customCleanupTasks.length = 0;
// 3. 最后移除清理事件监听器自身
console.log(`[文章脚本:${scriptInstanceId}] 移除清理监听器,数量:`, cleanupListeners.length);
cleanupListeners.forEach(({ element, eventType, handler, options }) => {
try {
console.log(`[文章脚本:${scriptInstanceId}] 移除清理监听器: ${eventType} 从`, element.tagName || "Window/Document");
element.removeEventListener(eventType, handler, options);
} catch (err) {
console.error(`[文章脚本:${scriptInstanceId}] 移除清理监听器出错:`, err);
}
});
console.log(`[文章脚本:${scriptInstanceId}] 完全销毁完成`);
} }
// 代码块复制功能 // 注册清理事件,并保存引用
function setupCodeCopy() { function registerCleanupEvents() {
const copyButtons = document.querySelectorAll('.code-block-copy'); console.log(`[文章脚本:${scriptInstanceId}] 注册清理事件`);
if (copyButtons.length === 0) return;
// 创建一次性事件处理函数
const beforeSwapHandler = () => {
console.log(`[文章脚本:${scriptInstanceId}] astro:before-swap 触发,执行自销毁`);
selfDestruct();
};
const beforeUnloadHandler = () => {
console.log(`[文章脚本:${scriptInstanceId}] beforeunload 触发,执行自销毁`);
selfDestruct();
};
// 添加清理事件监听器并保存引用
document.addEventListener("astro:before-swap", beforeSwapHandler, { once: true });
window.addEventListener("beforeunload", beforeUnloadHandler, { once: true });
// 保存清理事件引用,用于完全销毁
cleanupListeners.push(
{ element: document, eventType: "astro:before-swap", handler: beforeSwapHandler, options: { once: true } },
{ element: window, eventType: "beforeunload", handler: beforeUnloadHandler, options: { once: true } }
);
}
// 初始化所有功能
function initializeFeatures() {
console.log(`[文章脚本:${scriptInstanceId}] 开始初始化各功能`);
// 1. 代码块复制功能
function setupCodeCopy() {
console.log(`[文章脚本:${scriptInstanceId}] 初始化代码复制功能`);
const copyButtons = document.querySelectorAll('.code-block-copy');
if (copyButtons.length === 0) {
console.log(`[文章脚本:${scriptInstanceId}] 未找到代码复制按钮`);
return;
}
console.log(`[文章脚本:${scriptInstanceId}] 找到代码复制按钮数量:`, copyButtons.length);
copyButtons.forEach(button => { copyButtons.forEach(button => {
addListener(button, 'click', async () => { addListener(button, 'click', async () => {
try { try {
// 使用Base64解码获取代码文本
const encodedCode = button.getAttribute('data-code'); const encodedCode = button.getAttribute('data-code');
if (!encodedCode) return; if (!encodedCode) return;
// 解码并复制到剪贴板
const code = atob(encodedCode); const code = atob(encodedCode);
await navigator.clipboard.writeText(code); await navigator.clipboard.writeText(code);
@ -530,7 +613,7 @@ const tableOfContents = generateTableOfContents(headings);
button.innerHTML = originalHTML; button.innerHTML = originalHTML;
}, 2000); }, 2000);
} catch (err) { } catch (err) {
console.error('复制失败:', err); console.error(`[文章脚本:${scriptInstanceId}] 复制失败:`, err);
button.innerHTML = ` button.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4">
<circle cx="12" cy="12" r="10"></circle> <circle cx="12" cy="12" r="10"></circle>
@ -553,12 +636,16 @@ const tableOfContents = generateTableOfContents(headings);
}); });
} }
// 4. 设置阅读进度条 // 2. 阅读进度条
function setupProgressBar() { function setupProgressBar() {
console.log(`[文章脚本:${scriptInstanceId}] 初始化阅读进度条`);
const progressBar = document.getElementById("progress-bar"); const progressBar = document.getElementById("progress-bar");
const backToTopButton = document.getElementById("back-to-top"); const backToTopButton = document.getElementById("back-to-top");
if (!progressBar) return; if (!progressBar) {
console.warn(`[文章脚本:${scriptInstanceId}] 未找到进度条元素`);
return;
}
function updateReadingProgress() { function updateReadingProgress() {
const scrollTop = window.scrollY || document.documentElement.scrollTop; const scrollTop = window.scrollY || document.documentElement.scrollTop;
@ -605,15 +692,20 @@ const tableOfContents = generateTableOfContents(headings);
}); });
} }
// 初始更新一次进度条
updateReadingProgress(); updateReadingProgress();
} }
// 5. 管理目录交互 // 3. 目录交互
function setupTableOfContents() { function setupTableOfContents() {
console.log(`[文章脚本:${scriptInstanceId}] 初始化目录交互`);
const tocContent = document.getElementById("toc-content"); const tocContent = document.getElementById("toc-content");
const tocPanel = document.querySelector("#toc-panel"); const tocPanel = document.querySelector("#toc-panel");
if (!tocPanel || !tocContent) return; if (!tocPanel || !tocContent) {
console.warn(`[文章脚本:${scriptInstanceId}] 未找到目录面板或内容`);
return;
}
// 检查窗口大小调整目录面板显示 // 检查窗口大小调整目录面板显示
function checkTocVisibility() { function checkTocVisibility() {
@ -631,6 +723,8 @@ const tableOfContents = generateTableOfContents(headings);
// 处理目录链接点击跳转 // 处理目录链接点击跳转
const tocLinks = tocContent.querySelectorAll("a"); const tocLinks = tocContent.querySelectorAll("a");
console.log(`[文章脚本:${scriptInstanceId}] 找到目录链接数量:`, tocLinks.length);
tocLinks.forEach(link => { tocLinks.forEach(link => {
addListener(link, "click", (e) => { addListener(link, "click", (e) => {
e.preventDefault(); e.preventDefault();
@ -657,7 +751,10 @@ const tableOfContents = generateTableOfContents(headings);
// 监听滚动以更新当前活动的目录项 // 监听滚动以更新当前活动的目录项
const article = document.querySelector("article"); const article = document.querySelector("article");
if (!article) return; if (!article) {
console.warn(`[文章脚本:${scriptInstanceId}] 未找到文章内容元素`);
return;
}
let ticking = false; let ticking = false;
@ -725,25 +822,34 @@ const tableOfContents = generateTableOfContents(headings);
updateActiveHeading(); updateActiveHeading();
} }
// 6. 处理Mermaid图表渲染 // 4. Mermaid图表渲染
function setupMermaid() { function setupMermaid() {
// 查找所有mermaid代码块 - 支持多种可能的类名和选择器 console.log(`[文章脚本:${scriptInstanceId}] 检查Mermaid图表`);
// 查找所有mermaid代码块
const mermaidBlocks = document.querySelectorAll( const mermaidBlocks = document.querySelectorAll(
'pre.language-mermaid, pre > code.language-mermaid, .mermaid' 'pre.language-mermaid, pre > code.language-mermaid, .mermaid'
); );
if (mermaidBlocks.length === 0) return; if (mermaidBlocks.length === 0) {
console.log(`[文章脚本:${scriptInstanceId}] 未找到Mermaid图表`);
return;
}
console.log('找到Mermaid代码块:', mermaidBlocks.length); console.log(`[文章脚本:${scriptInstanceId}] 找到Mermaid图表数量:`, mermaidBlocks.length);
// 动态加载mermaid库 // 动态加载mermaid库
const script = document.createElement('script'); const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js'; script.src = 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js';
script.onload = function() { script.onload = function() {
console.log('Mermaid库加载完成开始渲染图表'); console.log(`[文章脚本:${scriptInstanceId}] Mermaid库加载成功`);
// 初始化mermaid配置 - 始终使用默认主题通过CSS控制样式 if (!window.mermaid) {
console.error(`[文章脚本:${scriptInstanceId}] Mermaid库加载后window.mermaid不存在`);
return;
}
// 初始化mermaid配置
window.mermaid.initialize({ window.mermaid.initialize({
startOnLoad: false, startOnLoad: false,
theme: 'default', theme: 'default',
@ -789,17 +895,17 @@ const tableOfContents = generateTableOfContents(headings);
// 初始化渲染 // 初始化渲染
try { try {
console.log('开始渲染Mermaid图表'); console.log(`[文章脚本:${scriptInstanceId}] 开始渲染Mermaid图表`);
window.mermaid.run().catch(err => { window.mermaid.run().catch(err => {
console.error('Mermaid渲染出错:', err); console.error(`[文章脚本:${scriptInstanceId}] Mermaid渲染出错:`, err);
}); });
} catch (error) { } catch (error) {
console.error('初始化Mermaid渲染失败:', error); console.error(`[文章脚本:${scriptInstanceId}] 初始化Mermaid渲染失败:`, error);
} }
}; };
script.onerror = function() { script.onerror = function() {
console.error('加载Mermaid库失败'); console.error(`[文章脚本:${scriptInstanceId}] 加载Mermaid库失败`);
// 显示错误信息 // 显示错误信息
mermaidBlocks.forEach(block => { mermaidBlocks.forEach(block => {
if (block.tagName === 'CODE') block = block.closest('pre'); if (block.tagName === 'CODE') block = block.closest('pre');
@ -811,18 +917,27 @@ const tableOfContents = generateTableOfContents(headings);
document.head.appendChild(script); document.head.appendChild(script);
// 添加到清理列表,确保后续页面跳转时能删除脚本 // 添加Mermaid清理任务
listeners.push({ customCleanupTasks.push(() => {
element: script, console.log(`[文章脚本:${scriptInstanceId}] 执行Mermaid特殊清理`);
eventType: 'remove',
handler: () => { // 移除脚本标签
if (script.parentNode) { if (script.parentNode) {
script.parentNode.removeChild(script); script.parentNode.removeChild(script);
} }
// 清除全局mermaid对象 // 清除全局mermaid对象
if (window.mermaid) { if (window.mermaid) {
console.log(`[文章脚本:${scriptInstanceId}] 清除window.mermaid对象`);
try {
// 尝试清理mermaid内部状态
if (typeof window.mermaid.destroy === 'function') {
window.mermaid.destroy();
}
window.mermaid = undefined; window.mermaid = undefined;
} catch (e) {
console.error(`[文章脚本:${scriptInstanceId}] 清理mermaid对象出错:`, e);
}
} }
// 移除页面上可能留下的mermaid相关元素 // 移除页面上可能留下的mermaid相关元素
@ -837,70 +952,27 @@ const tableOfContents = generateTableOfContents(headings);
document.querySelectorAll(mermaidElements.join(', ')).forEach(el => { document.querySelectorAll(mermaidElements.join(', ')).forEach(el => {
if (el && el.parentNode) { if (el && el.parentNode) {
console.log(`[文章脚本:${scriptInstanceId}] 移除Mermaid元素:`, el.id || el.className);
el.parentNode.removeChild(el); el.parentNode.removeChild(el);
} }
}); });
} catch (e) { } catch (e) {
console.error('清理Mermaid元素时出错:', e); console.error(`[文章脚本:${scriptInstanceId}] 清理Mermaid元素时出错:`, e);
} }
},
options: null
}); });
} }
// 初始化所有功能 // 启动所有功能
function init() { setupCodeCopy();
if (!document.querySelector("article")) return;
setupCodeCopy(); // 只保留代码复制功能
setupProgressBar(); setupProgressBar();
setupTableOfContents(); setupTableOfContents();
setupMermaid(); setupMermaid();
console.log(`[文章脚本:${scriptInstanceId}] 所有功能初始化完成`);
} }
// 注册清理函数 // 执行初始化
function registerCleanup() { registerCleanupEvents();
// 使用 once: true 确保事件只触发一次 initializeFeatures();
document.addEventListener("astro:before-preparation", cleanup, { once: true });
document.addEventListener("astro:before-swap", cleanup, { once: true });
document.addEventListener("swup:willReplaceContent", cleanup, { once: true });
window.addEventListener("beforeunload", cleanup, { once: true });
}
// 处理页面跳转事件
function setupPageTransitionEvents() {
// 页面转换后事件
const pageTransitionEvents = [
{ name: "astro:after-swap", delay: 10 },
{ name: "astro:page-load", delay: 10 },
{ name: "swup:contentReplaced", delay: 10 },
];
// 设置每个页面转换事件
pageTransitionEvents.forEach(({ name, delay }) => {
document.addEventListener(name, () => {
cleanup(); // 立即清理
// 延迟初始化确保DOM完全更新
setTimeout(() => {
cleanup(); // 再次清理,确保没有遗漏
init();
}, delay);
});
});
}
// 页面加载后初始化
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
init();
registerCleanup();
setupPageTransitionEvents();
}, { once: true });
} else {
init();
registerCleanup();
setupPageTransitionEvents();
}
})(); })();
</script> </script>

View File

@ -1,7 +1,7 @@
/* 代码块容器样式 - 简化背景和阴影 */ /* 代码块容器样式 - 简化背景和阴影 */
.code-block-container { .code-block-container {
margin: 1rem 0; margin: 0.75rem 0;
border-radius: 0.5rem; border-radius: 0.4rem;
overflow: hidden; overflow: hidden;
border: 1px solid #e2e8f0; border: 1px solid #e2e8f0;
background-color: transparent; background-color: transparent;
@ -13,11 +13,11 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 0.4rem 0.8rem; padding: 0.3rem 0.6rem;
background-color: #f1f5f9; background-color: #f1f5f9;
border-bottom: 1px solid #e2e8f0; border-bottom: 1px solid #e2e8f0;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 0.875rem; font-size: 0.8rem;
} }
/* 代码语言标签 */ /* 代码语言标签 */
@ -42,10 +42,10 @@
color: #475569; color: #475569;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.25rem; gap: 0.2rem;
padding: 0.25rem 0.5rem; padding: 0.2rem 0.4rem;
border-radius: 0.25rem; border-radius: 0.25rem;
font-size: 0.75rem; font-size: 0.7rem;
transition: all 0.2s ease; transition: all 0.2s ease;
} }
@ -67,14 +67,14 @@
/* 基础代码块样式 - 减小内边距 */ /* 基础代码块样式 - 减小内边距 */
pre { pre {
margin: 0; margin: 0;
padding: 0.2rem 0; padding: 0.15rem 0;
overflow-x: auto; overflow-x: auto;
} }
pre code { pre code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1.05rem; font-size: 0.9rem;
line-height: 1.5rem; line-height: 1.4rem;
padding: 0; padding: 0;
display: block; display: block;
} }
@ -92,7 +92,7 @@ pre code {
left: 0; left: 0;
top: 0; top: 0;
bottom: 0; bottom: 0;
width: 3rem; width: 2.5rem;
background-color: #f1f5f9; background-color: #f1f5f9;
border-right: 1px solid #e2e8f0; border-right: 1px solid #e2e8f0;
z-index: 1; z-index: 1;
@ -102,9 +102,9 @@ pre code {
.line-numbers .line { .line-numbers .line {
position: relative; position: relative;
counter-increment: line; counter-increment: line;
padding-left: 3.5rem; padding-left: 3rem;
padding-right: 0.5rem; padding-right: 0.4rem;
min-height: 1.5rem; min-height: 1.4rem;
white-space: pre; white-space: pre;
} }
@ -114,11 +114,11 @@ pre code {
position: absolute; position: absolute;
left: 0; left: 0;
top: 0; top: 0;
width: 3rem; width: 2.5rem;
height: 100%; height: 100%;
text-align: center; text-align: center;
color: #94a3b8; color: #94a3b8;
font-size: 0.95rem; font-size: 0.85rem;
user-select: none; user-select: none;
z-index: 2; z-index: 2;
display: flex; display: flex;
@ -196,3 +196,57 @@ pre.shiki, pre.astro-code,
color: var(--shiki-dark) !important; color: var(--shiki-dark) !important;
} }
/* 行内代码块样式 */
:not(pre) > code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 0.875rem;
color: var(--color-primary-700);
background-color: var(--color-primary-50);
padding: 0.2rem 0.4rem;
margin: 0 0.2rem;
border-radius: 0.3rem;
border: 1px solid var(--color-primary-100);
white-space: normal;
word-wrap: break-word;
overflow-wrap: break-word;
word-break: break-all;
max-width: 100%;
display: inline-block;
vertical-align: middle;
line-height: 1.4;
}
/* 行内代码块黑暗模式样式 */
[data-theme='dark'] :not(pre) > code {
color: var(--color-primary-300);
background-color: rgba(75, 107, 255, 0.1);
border-color: var(--color-primary-700);
}
/* 长路径的行内代码块样式特殊处理 */
:not(pre) > code:has(path),
:not(pre) > code.file-path {
white-space: pre-wrap; /* 保留空格但允许换行 */
overflow-wrap: break-word; /* 允许在任何地方断行 */
word-break: break-all; /* 允许在任何字符间断行 */
max-width: 100%;
display: inline-block;
font-size: 0.85rem; /* 略微减小字体尺寸 */
padding: 0.2rem 0.4rem;
line-height: 1.4;
}
/* 针对文件路径的特殊样式 - 适用于Windows路径 */
:not(pre) > code.file-path {
color: var(--color-gray-700);
background-color: var(--color-gray-100);
border-color: var(--color-gray-200);
}
/* 针对文件路径的特殊样式 - 黑暗模式 */
[data-theme='dark'] :not(pre) > code.file-path {
color: var(--color-gray-300);
background-color: var(--color-gray-800);
border-color: var(--color-gray-700);
}

View File

@ -140,7 +140,7 @@ fn get_search_suggestions(search_index: &ArticleSearchIndex, query: &str) -> Vec
suggestion_type: SuggestionType::Completion, suggestion_type: SuggestionType::Completion,
frequency: *freq frequency: *freq
}); });
} else if query.starts_with(&term_lower) || term_lower.contains(&query) { } else if term_lower.contains(&query) {
// 包含关系,作为纠正建议 // 包含关系,作为纠正建议
candidates.push(SuggestionCandidate { candidates.push(SuggestionCandidate {
text: term.clone(), text: term.clone(),