diff --git a/astro.config.mjs b/astro.config.mjs index e21ac6d..27f818b 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -15,6 +15,7 @@ import compressor from "astro-compressor"; import vercel from "@astrojs/vercel"; import { articleIndexerIntegration } from "./src/plugins/build-article-index.js"; import { rehypeCodeBlocks } from "./src/plugins/rehype-code-blocks.js"; +import { rehypeTables } from "./src/plugins/rehype-tables.js"; function getArticleDate(articleId) { try { @@ -129,7 +130,8 @@ export default defineConfig({ ], rehypePlugins: [ [rehypeExternalLinks, { target: '_blank', rel: ['nofollow', 'noopener', 'noreferrer'] }], - rehypeCodeBlocks + rehypeCodeBlocks, + rehypeTables ], gfm: true, }, diff --git a/src/components/Search.tsx b/src/components/Search.tsx index ae576aa..b5c5243 100644 --- a/src/components/Search.tsx +++ b/src/components/Search.tsx @@ -507,9 +507,48 @@ const Search: React.FC = ({ } } - // 回车键直接执行搜索 + // 回车键处理逻辑 if (e.key === "Enter") { e.preventDefault(); + + // 情况1: 如果有当前选中的内联建议(推荐或纠正) + if (inlineSuggestion.visible && inlineSuggestion.text) { + const suggestionText = inlineSuggestion.text; + + // 先检查当前搜索结果中是否有完全匹配的结果 + const exactMatchForSuggestion = allItems.find(item => + item.title.replace(/<\/?mark>/g, '').toLowerCase() === suggestionText.toLowerCase() + ); + + if (exactMatchForSuggestion) { + // 如果有完全匹配的结果,直接导航 + window.location.href = exactMatchForSuggestion.url; + return; + } + + // 没有完全匹配,先补全建议并导航到第一个结果 + completeInlineSuggestion(true); // 传入true表示需要导航到第一个结果 + return; + } + // 情况2: 如果没有内联建议,但有搜索结果 + else if (allItems.length > 0 && query.trim()) { + // 尝试找到完全匹配当前查询的结果 + const exactMatch = allItems.find(item => + item.title.replace(/<\/?mark>/g, '').toLowerCase() === query.trim().toLowerCase() + ); + + if (exactMatch) { + // 找到完全匹配,直接导航到该文章 + window.location.href = exactMatch.url; + return; + } + + // 如果没有完全匹配,但有搜索结果,进入第一个结果 + window.location.href = allItems[0].url; + return; + } + + // 如果以上条件都不满足,执行普通搜索 performSearch(query, false); } }; @@ -561,7 +600,7 @@ const Search: React.FC = ({ }, [updateCaretPosition]); // 执行搜索 - const performSearch = async (searchQuery: string, isLoadMore: boolean = false) => { + const performSearch = async (searchQuery: string, isLoadMore: boolean = false, shouldNavigateToFirstResult: boolean = false) => { if (!wasmModule || !isIndexLoaded || !indexData || !searchQuery.trim()) { return; } @@ -639,6 +678,11 @@ const Search: React.FC = ({ // 更新加载状态 setLoadingState(prev => ({ ...prev, status: 'success' })); + + // 如果需要导航到第一个结果,并且有结果 + if (shouldNavigateToFirstResult && result.items.length > 0) { + window.location.href = result.items[0].url; + } } catch (err) { // 检查组件是否仍然挂载 if (!isMountedRef.current) return; @@ -652,7 +696,7 @@ const Search: React.FC = ({ }; // 自动补全内联建议 - 不使用useCallback,避免循环依赖 - const completeInlineSuggestion = () => { + const completeInlineSuggestion = (shouldNavigateToFirstResult = false) => { if (inlineSuggestion.visible && inlineSuggestion.text) { // 保存建议文本 const textToComplete = inlineSuggestion.text; @@ -678,7 +722,7 @@ const Search: React.FC = ({ setQuery(textToComplete); // 立即执行搜索 - performSearch(textToComplete, false); + performSearch(textToComplete, false, shouldNavigateToFirstResult); // 聚焦输入框并设置光标位置 if (searchInputRef.current) { diff --git a/src/consts.ts b/src/consts.ts index 0979ea5..58fa8b3 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -37,10 +37,10 @@ export const NAV_STRUCTURE = [ ] }, { - id: 'others', + id: 'other', text: '其他', items: [ - { id: 'other', text: '其他', href: '/other' }, + { id: 'about', text: '关于', href: '/other' }, { id: 'projects', text: '项目', href: '/projects' } ] } diff --git a/src/plugins/rehype-code-blocks.js b/src/plugins/rehype-code-blocks.js index 1898d0d..215e095 100644 --- a/src/plugins/rehype-code-blocks.js +++ b/src/plugins/rehype-code-blocks.js @@ -200,6 +200,20 @@ export function rehypeCodeBlocks() { { type: 'text', value: ' 复制' } ]; + // 计算代码行数,用于生成行号 + const lineCount = originalCode.split('\n').length; + + // 生成行号元素 + const lineNumberElements = []; + for (let i = 1; i <= lineCount; i++) { + lineNumberElements.push({ + type: 'element', + tagName: 'div', + properties: { className: ['line-number'] }, + children: [{ type: 'text', value: String(i) }] + }); + } + // 创建新的代码块容器结构 const codeBlockContainer = { type: 'element', @@ -235,25 +249,40 @@ export function rehypeCodeBlocks() { } ] }, - // 代码内容区域 - 保留原始结构 + // 代码内容区域 - 修改结构,将代码内容和行号分离 { type: 'element', tagName: 'div', properties: { className: ['code-block-content'] }, children: [ - // 保留原始的 pre 元素及其所有关键属性 + // 行号容器 { type: 'element', - tagName: 'pre', - properties: { - style: nodeStyle, - className: nodeClasses, - // 其他可能的重要属性 - 'data-theme': node.properties['data-theme'] - }, + tagName: 'div', + properties: { className: ['line-numbers-container'] }, + children: lineNumberElements + }, + // 代码内容容器 + { + type: 'element', + tagName: 'div', + properties: { className: ['code-content-container'] }, children: [ - // 保留原始的code元素,不做修改 - codeElement + // 保留原始的 pre 元素及其所有关键属性 + { + type: 'element', + tagName: 'pre', + properties: { + style: nodeStyle, + className: nodeClasses, + // 其他可能的重要属性 + 'data-theme': node.properties['data-theme'] + }, + children: [ + // 保留原始的code元素,不做修改 + codeElement + ] + } ] } ] diff --git a/src/plugins/rehype-tables.js b/src/plugins/rehype-tables.js new file mode 100644 index 0000000..84f1386 --- /dev/null +++ b/src/plugins/rehype-tables.js @@ -0,0 +1,22 @@ +import { visit } from 'unist-util-visit'; + +export function rehypeTables() { + return (tree) => { + visit(tree, 'element', (node, index, parent) => { + if (node.tagName === 'table') { + // 创建表格容器 + const tableContainer = { + type: 'element', + tagName: 'div', + properties: { + className: ['table-container'] + }, + children: [node] + }; + + // 替换原始表格节点 + parent.children[index] = tableContainer; + } + }); + }; +} \ No newline at end of file diff --git a/src/styles/articles-code.css b/src/styles/articles-code.css index ba96530..944492d 100644 --- a/src/styles/articles-code.css +++ b/src/styles/articles-code.css @@ -57,18 +57,51 @@ color: #10b981; } -/* 代码内容容器 - 移除背景 */ +/* 代码内容容器 - 修改为 flex 布局 */ .code-block-content { position: relative; - overflow-x: auto; + display: flex; background-color: transparent; } +/* 行号容器 - 固定宽度和位置 */ +.line-numbers-container { + flex: 0 0 2.5rem; + background-color: #f1f5f9; + border-right: 1px solid #e2e8f0; + z-index: 1; + position: sticky; + left: 0; + display: flex; + flex-direction: column; + align-items: center; + padding: 0.15rem 0; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +/* 行号元素样式 */ +.line-number { + width: 100%; + height: 1.4rem; + display: flex; + align-items: center; + justify-content: center; + color: #94a3b8; + font-size: 0.85rem; + user-select: none; +} + +/* 代码内容容器 - 允许水平滚动 */ +.code-content-container { + flex: 1; + overflow-x: auto; +} + /* 基础代码块样式 - 减小内边距 */ pre { margin: 0; padding: 0.15rem 0; - overflow-x: auto; + overflow-x: visible; /* 改为 visible,滚动由父容器处理 */ } pre code { @@ -85,47 +118,16 @@ pre code { position: relative; } -/* 行号背景条 - 减小宽度 */ -.line-numbers::before { - content: ""; - position: absolute; - left: 0; - top: 0; - bottom: 0; - width: 2.5rem; - background-color: #f1f5f9; - border-right: 1px solid #e2e8f0; - z-index: 1; -} - -/* 行样式 - 进一步减小行间距和缩进 */ +/* 行样式 - 移除左侧 padding */ .line-numbers .line { position: relative; counter-increment: line; - padding-left: 3rem; + padding-left: 0.5rem; /* 减小左侧内边距 */ padding-right: 0.4rem; min-height: 1.4rem; white-space: pre; } -/* 行号 - 调整位置 */ -.line-numbers .line::before { - content: counter(line); - position: absolute; - left: 0; - top: 0; - width: 2.5rem; - height: 100%; - text-align: center; - color: #94a3b8; - font-size: 0.85rem; - user-select: none; - z-index: 2; - display: flex; - align-items: center; - justify-content: center; -} - /* 暗色模式 */ [data-theme='dark'] .code-block-container { border-color: #334155; @@ -154,17 +156,17 @@ pre code { background-color: transparent; } -/* 暗色模式行号样式 */ -[data-theme='dark'] .line-numbers::before { +/* 暗色模式行号容器样式 */ +[data-theme='dark'] .line-numbers-container { background-color: #1e293b; border-right-color: #334155; } -[data-theme='dark'] .line-numbers .line::before { +/* 暗色模式行号样式 */ +[data-theme='dark'] .line-number { color: #64748b; } - /* 确保所有代码元素没有背景 */ code, pre, .code-block-content, .code-block-content pre.shiki, @@ -201,11 +203,8 @@ pre.shiki, pre.astro-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; @@ -219,8 +218,6 @@ pre.shiki, pre.astro-code, /* 行内代码块黑暗模式样式 */ [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); } /* 长路径的行内代码块样式特殊处理 */ @@ -239,14 +236,10 @@ pre.shiki, pre.astro-code, /* 针对文件路径的特殊样式 - 适用于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); } diff --git a/src/styles/articles-table.css b/src/styles/articles-table.css index 4938cf7..58d1c27 100644 --- a/src/styles/articles-table.css +++ b/src/styles/articles-table.css @@ -1,13 +1,14 @@ /* 表格样式 - 与全局主题配色协调 */ -table { +.table-container { + container-type: inline-size; width: 100%; - margin-bottom: 1.5rem; - border-collapse: separate; - border-spacing: 0; - border-radius: 0.5rem; overflow-x: auto; - display: block; - max-width: 100%; + border-radius: 0.5rem; +} + +table { + display: table; + width: 100%; } thead {