From a1a4d7449304b1adf3e9b26ef82c3f29f50c5c8c Mon Sep 17 00:00:00 2001 From: lsy Date: Fri, 28 Mar 2025 22:14:16 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=BC=80=E6=BB=9A=E5=8A=A8=E6=9D=A1?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=EF=BC=8C=E6=90=9C=E7=B4=A2=E5=86=85=E5=AE=B9?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E5=A4=8D=E4=BB=A3=E7=A0=81=E5=9D=97=E8=A7=A3?= =?UTF-8?q?=E6=9E=90html=EF=BC=8C=E8=A1=A8=E6=83=85=E5=91=A8=E5=9B=B4?= =?UTF-8?q?=E6=9C=89=E7=A9=BA=E6=A0=BC=E6=89=8D=E4=BC=9A=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astro.config.mjs | 116 ++++++++++++++++++-------------- src/components/Footer.astro | 2 +- src/components/header.astro | 76 ++++++++++++++++++--- src/content/web/MDX使用教程.mdx | 61 ++++------------- src/pages/api/search.ts | 30 ++++++--- src/styles/content-styles.css | 19 ++++++ src/styles/global.css | 67 ++++++++++++++++++ src/styles/prism.css | 2 - 8 files changed, 252 insertions(+), 121 deletions(-) diff --git a/astro.config.mjs b/astro.config.mjs index 89f3041..c3f26b9 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,35 +1,35 @@ // @ts-check -import { defineConfig } from 'astro/config'; +import { defineConfig } from "astro/config"; -import tailwindcss from '@tailwindcss/vite'; -import mdx from '@astrojs/mdx'; -import react from '@astrojs/react'; -import remarkEmoji from 'remark-emoji'; -import rehypeExternalLinks from 'rehype-external-links'; -import sitemap from '@astrojs/sitemap'; -import fs from 'node:fs'; -import path from 'node:path'; -import { SITE_URL } from './src/consts'; +import tailwindcss from "@tailwindcss/vite"; +import mdx from "@astrojs/mdx"; +import react from "@astrojs/react"; +import remarkEmoji from "remark-emoji"; +import rehypeExternalLinks from "rehype-external-links"; +import sitemap from "@astrojs/sitemap"; +import fs from "node:fs"; +import path from "node:path"; +import { SITE_URL } from "./src/consts"; -import vercel from '@astrojs/vercel'; +import vercel from "@astrojs/vercel"; function getArticleDate(articleId) { try { // 处理多级目录的文章路径 - const mdPath = path.join(process.cwd(), 'src/content', articleId + '.md'); - const mdxPath = path.join(process.cwd(), 'src/content', articleId + '.mdx'); - + const mdPath = path.join(process.cwd(), "src/content", articleId + ".md"); + const mdxPath = path.join(process.cwd(), "src/content", articleId + ".mdx"); + let filePath = fs.existsSync(mdPath) ? mdPath : mdxPath; - + if (fs.existsSync(filePath)) { - const content = fs.readFileSync(filePath, 'utf-8'); + const content = fs.readFileSync(filePath, "utf-8"); const match = content.match(/date:\s*(\d{4}-\d{2}-\d{2})/); if (match) { return new Date(match[1]).toISOString(); } } } catch (error) { - console.error('Error reading article date:', error); + console.error("Error reading article date:", error); } return new Date().toISOString(); // 如果没有日期,返回当前时间 } @@ -37,11 +37,11 @@ function getArticleDate(articleId) { // https://astro.build/config export default defineConfig({ site: SITE_URL, - output: 'static', - trailingSlash: 'ignore', + output: "static", + trailingSlash: "ignore", build: { - format: 'directory' + format: "directory", }, vite: { @@ -52,94 +52,106 @@ export default defineConfig({ // 手动分块配置 manualChunks: { // 将地图组件单独打包 - 'world-heatmap': ['./src/components/WorldHeatmap.tsx'], + "world-heatmap": ["./src/components/WorldHeatmap.tsx"], // 将 React 相关库单独打包 - 'react-vendor': ['react', 'react-dom'], + "react-vendor": ["react", "react-dom"], // 其他大型依赖也可以单独打包 - 'chart-vendor': ['chart.js'], + "chart-vendor": ["chart.js"], // 将 ECharts 单独打包 - 'echarts-vendor': ['echarts'], + "echarts-vendor": ["echarts"], // 将其他组件打包到一起 - 'components': ['./src/components'] - } - } + components: ["./src/components"], + }, + }, }, // 提高警告阈值,避免不必要的警告 - chunkSizeWarningLimit: 1000 - } + chunkSizeWarningLimit: 1000, + }, }, integrations: [ + // MDX 集成配置 mdx({ - syntaxHighlight: 'prism', + // 不使用共享的 markdown 配置 + extendMarkdownConfig: false, + // 为 MDX 单独配置所需功能 remarkPlugins: [ - [remarkEmoji, { emoticon: true }] + // 添加表情符号支持 + [remarkEmoji, { emoticon: true, padded: true }] ], rehypePlugins: [ [rehypeExternalLinks, { target: '_blank', rel: ['nofollow', 'noopener', 'noreferrer'] }] ], - gfm: true, - shikiConfig: { - theme: 'github-dark', - langs: [], - wrap: true, - } + // 设置代码块处理行为 + remarkRehype: { + allowDangerousHtml: false // 不解析 HTML + }, + gfm: true }), react(), sitemap({ - filter: (page) => !page.includes('/api/'), + filter: (page) => !page.includes("/api/"), serialize(item) { if (!item) return undefined; // 文章页面 - if (item.url.includes('/articles/')) { + if (item.url.includes("/articles/")) { // 从 URL 中提取文章 ID - const articleId = item.url.replace(SITE_URL + '/articles/', '').replace(/\/$/, ''); + const articleId = item.url + .replace(SITE_URL + "/articles/", "") + .replace(/\/$/, ""); const publishDate = getArticleDate(articleId); return { ...item, priority: 0.8, - lastmod: publishDate + lastmod: publishDate, }; } // 其他页面 else { let priority = 0.7; // 默认优先级 - + // 首页最高优先级 - if (item.url === SITE_URL + '/') { + if (item.url === SITE_URL + "/") { priority = 1.0; } // 文章列表页次高优先级 - else if (item.url === SITE_URL + '/articles/') { + else if (item.url === SITE_URL + "/articles/") { priority = 0.9; } return { ...item, - priority + priority, }; } - } - }) + }, + }), ], // Markdown 配置 markdown: { syntaxHighlight: 'prism', remarkPlugins: [ - [remarkEmoji, { emoticon: true }] + [remarkEmoji, { emoticon: true, padded: true }] ], rehypePlugins: [ [rehypeExternalLinks, { target: '_blank', rel: ['nofollow', 'noopener', 'noreferrer'] }] ], gfm: true, + // 设置 remark-rehype 选项,以控制HTML处理 + remarkRehype: { + // 保留原始HTML格式,但仅在非代码块区域 + allowDangerousHtml: true, + // 确保代码块内容不被解析 + passThrough: ['code'] + }, shikiConfig: { - theme: 'github-dark', + theme: "github-dark", langs: [], wrap: true, - } + }, }, - adapter: vercel() -}); \ No newline at end of file + adapter: vercel(), +}); diff --git a/src/components/Footer.astro b/src/components/Footer.astro index db1590f..3991c06 100644 --- a/src/components/Footer.astro +++ b/src/components/Footer.astro @@ -43,7 +43,7 @@ const currentYear = new Date().getFullYear();
- © {currentYear} New Echoes. All rights reserved. + © {currentYear} New Echoes. All rights reserved. · $1'); + } + // 搜索文章 function searchArticles(query: string, resultsList: HTMLElement, resultsMessage: HTMLElement) { if (!query.trim()) { @@ -339,16 +351,18 @@ const normalizedPath = currentPath.endsWith('/') ? currentPath.slice(0, -1) : cu // 过滤并排序结果 const filteredArticles = articles - .filter(article => { + .filter((article: Article) => { const title = article.title.toLowerCase(); - const tags = article.tags ? article.tags.map(tag => tag.toLowerCase()) : []; + const tags = article.tags ? article.tags.map((tag: string) => tag.toLowerCase()) : []; const summary = article.summary ? article.summary.toLowerCase() : ''; + const content = article.content ? article.content.toLowerCase() : ''; return title.includes(lowerQuery) || - tags.some(tag => tag.includes(lowerQuery)) || - summary.includes(lowerQuery); + tags.some((tag: string) => tag.includes(lowerQuery)) || + summary.includes(lowerQuery) || + content.includes(lowerQuery); }) - .sort((a, b) => { + .sort((a: Article, b: Article) => { // 标题匹配优先 const aTitle = a.title.toLowerCase(); const bTitle = b.title.toLowerCase(); @@ -360,6 +374,17 @@ const normalizedPath = currentPath.endsWith('/') ? currentPath.slice(0, -1) : cu return 1; } + // 内容匹配次之 + const aContent = a.content ? a.content.toLowerCase() : ''; + const bContent = b.content ? b.content.toLowerCase() : ''; + + if (aContent.includes(lowerQuery) && !bContent.includes(lowerQuery)) { + return -1; + } + if (!aContent.includes(lowerQuery) && bContent.includes(lowerQuery)) { + return 1; + } + // 日期排序 return new Date(b.date).getTime() - new Date(a.date).getTime(); }) @@ -374,14 +399,43 @@ const normalizedPath = currentPath.endsWith('/') ? currentPath.slice(0, -1) : cu // 显示结果 resultsMessage.style.display = 'none'; - resultsList.innerHTML = filteredArticles.map(article => ` + resultsList.innerHTML = filteredArticles.map((article: Article) => { + // 生成匹配的内容片段 + let contentMatch = ''; + if (article.content && article.content.toLowerCase().includes(lowerQuery)) { + // 找到匹配文本在内容中的位置 + const matchIndex = article.content.toLowerCase().indexOf(lowerQuery); + // 计算片段的起始和结束位置 + const startPos = Math.max(0, matchIndex - 50); + const endPos = Math.min(article.content.length, matchIndex + 100); + // 提取片段 + let snippet = article.content.substring(startPos, endPos); + // 如果不是从文章开头开始,添加省略号 + if (startPos > 0) { + snippet = '...' + snippet; + } + // 如果不是到文章结尾,添加省略号 + if (endPos < article.content.length) { + snippet = snippet + '...'; + } + // 高亮匹配的文本 + snippet = highlightText(snippet, query); + contentMatch = `

${snippet}

`; + } + + // 高亮标题和摘要中的匹配文本 + const highlightedTitle = highlightText(article.title, query); + const highlightedSummary = article.summary ? highlightText(article.summary, query) : ''; + + return `
  • - -

    ${article.title}

    - ${article.summary ? `

    ${article.summary}

    ` : ''} +
    +

    ${highlightedTitle}

    + ${article.summary ? `

    ${highlightedSummary}

    ` : ''} + ${contentMatch} ${article.tags && article.tags.length > 0 ? `
  • - `).join(''); + `}).join(''); } // 节流搜索 diff --git a/src/content/web/MDX使用教程.mdx b/src/content/web/MDX使用教程.mdx index f3320ea..a996c2d 100644 --- a/src/content/web/MDX使用教程.mdx +++ b/src/content/web/MDX使用教程.mdx @@ -201,6 +201,20 @@ function greet(user: User): string { --- +### 1.9 表情符号 + + +| 表情名称 | 语法 | 效果 | +|:--------|:-----|:-----| +| 笑脸 | `:smile:` | :smile: | +| 大笑 | `:laughing:` | :laughing: | +| 哭泣 | `:cry:` | :cry: | +| 心形 | `:heart:` | :heart: | +| 火箭 | `:rocket:` | :rocket: | +| 星星 | `:star:` | :star: | +| 警告 | `:warning:` | :warning: | +| 检查标记 | `:white_check_mark:` | :white_check_mark: | + ## 2. HTML/JSX 语法部分 ### 2.1 HTML 标签 @@ -241,50 +255,3 @@ function greet(user: User): string { -### 2.2 React 组件使用 - -```mdx -import { Button } from './Button' - - -``` - -### 2.3 导出和使用变量 - -```mdx -export const myVariable = "Hello MDX!" - -# {myVariable} -``` - -### 2.4 使用 JavaScript 表达式 - -```mdx -{new Date().getFullYear()} - -{['React', 'Vue', 'Angular'].map(framework => ( -
  • {framework}
  • -))} -``` - -### 2.5 组件属性传递 - -```mdx - - 这里是卡片内容 - -``` - -### 2.6 导入其他 MDX 文件 - -```mdx -import OtherContent from './other-content.mdx' - - -``` \ No newline at end of file diff --git a/src/pages/api/search.ts b/src/pages/api/search.ts index cb22c09..0df12aa 100644 --- a/src/pages/api/search.ts +++ b/src/pages/api/search.ts @@ -8,14 +8,28 @@ export async function GET() { // 过滤掉草稿文章,并转换为简化的数据结构 const formattedArticles = articles .filter(article => !article.data.draft) // 过滤掉草稿 - .map(article => ({ - id: article.id, - title: article.data.title, - date: article.data.date, - summary: article.data.summary || '', - tags: article.data.tags || [], - image: article.data.image || '' - })) + .map(article => { + // 提取文章内容,去除 Markdown 标记 + let contentText = ''; + if (article.body) { + contentText = article.body + .replace(/---[\s\S]*?---/, '') // 移除 frontmatter + .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // 将链接转换为纯文本 + .replace(/[#*`~>]/g, '') // 移除特殊字符 + .replace(/\n+/g, ' ') // 将换行转换为空格 + .trim(); + } + + return { + id: article.id, + title: article.data.title, + date: article.data.date, + summary: article.data.summary || '', + tags: article.data.tags || [], + image: article.data.image || '', + content: contentText // 添加文章内容 + }; + }) .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); // 按日期排序 return new Response(JSON.stringify(formattedArticles), { diff --git a/src/styles/content-styles.css b/src/styles/content-styles.css index 0569553..8cceac2 100644 --- a/src/styles/content-styles.css +++ b/src/styles/content-styles.css @@ -659,6 +659,25 @@ overflow: hidden; } +/* 添加 details 内部内容的内边距 */ +.prose details > *:not(summary) { + padding: 1.5em; + margin: 0; +} + +.prose details > p, +.prose details > ul, +.prose details > ol, +.prose details > div, +.prose details > pre { + margin-top: 0; + margin-bottom: 1em; +} + +.prose details > *:last-child { + margin-bottom: 0; +} + .prose details summary { padding: 1em; cursor: pointer; diff --git a/src/styles/global.css b/src/styles/global.css index bad810a..66b8bdf 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -41,6 +41,14 @@ --color-dark-border: #475569; --color-dark-text: #e2e8f0; --color-dark-text-secondary: #94a3b8; + + /* 滚动条颜色变量 */ + --scrollbar-track: #f1f5f9; + --scrollbar-thumb: #94a3b8; + --scrollbar-thumb-hover: #64748b; + --scrollbar-dark-track: #1e293b; + --scrollbar-dark-thumb: #475569; + --scrollbar-dark-thumb-hover: #64748b; } /* 深色模式样式 */ @@ -53,4 +61,63 @@ background-color: var(--bg-primary); color: var(--text-primary); +} + +/* 自定义滚动条样式 - 适用于所有浏览器 */ +/* 滚动条基础样式 */ +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +/* 滚动条轨道 */ +::-webkit-scrollbar-track { + background: var(--scrollbar-track); + border-radius: 8px; +} + +/* 滚动条滑块 */ +::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb); + border-radius: 8px; + border: 2px solid var(--scrollbar-track); + transition: background-color 0.2s ease; +} + +/* 滚动条滑块悬停 */ +::-webkit-scrollbar-thumb:hover { + background: var(--scrollbar-thumb-hover); +} + +/* 滚动条角落 */ +::-webkit-scrollbar-corner { + background: var(--scrollbar-track); +} + +/* 深色模式滚动条样式 */ +[data-theme='dark'] ::-webkit-scrollbar-track { + background: var(--scrollbar-dark-track); +} + +[data-theme='dark'] ::-webkit-scrollbar-thumb { + background: var(--scrollbar-dark-thumb); + border: 2px solid var(--scrollbar-dark-track); +} + +[data-theme='dark'] ::-webkit-scrollbar-thumb:hover { + background: var(--scrollbar-dark-thumb-hover); +} + +[data-theme='dark'] ::-webkit-scrollbar-corner { + background: var(--scrollbar-dark-track); +} + +/* Firefox 滚动条样式 */ +* { + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); +} + +[data-theme='dark'] * { + scrollbar-color: var(--scrollbar-dark-thumb) var(--scrollbar-dark-track); } \ No newline at end of file diff --git a/src/styles/prism.css b/src/styles/prism.css index daa1a8b..930fd34 100644 --- a/src/styles/prism.css +++ b/src/styles/prism.css @@ -135,5 +135,3 @@ pre[class*="language-"] { [data-theme="dark"] pre[class*="language-"] { background: #282c34 !important; } - -/* 添加其他token的暗色模式样式... */ \ No newline at end of file