将页面搜索和切换主题,代码块配色改为用别人的组件,添加seo和压缩代码组件,优化代码块样式,移除冗余样式和组件

This commit is contained in:
lsy 2025-04-25 00:01:44 +08:00
parent 6e96b59245
commit 25f4a89594
17 changed files with 28514 additions and 3126 deletions

View File

@ -11,9 +11,15 @@ import fs from "node:fs";
import path from "node:path";
import swup from "@swup/astro"
import { SITE_URL } from "./src/consts";
import pagefind from "astro-pagefind";
import compressor from "astro-compressor";
import vercel from "@astrojs/vercel";
import expressiveCode from "astro-expressive-code";
import { pluginLineNumbers } from "@expressive-code/plugin-line-numbers";
import { pluginCollapsibleSections } from "@expressive-code/plugin-collapsible-sections";
function getArticleDate(articleId) {
try {
// 处理多级目录的文章路径
@ -47,50 +53,55 @@ export default defineConfig({
vite: {
plugins: [tailwindcss()],
build: {
rollupOptions: {
output: {
// 手动分块配置
manualChunks: {
// 将地图组件单独打包
"world-heatmap": ["./src/components/WorldHeatmap.tsx"],
// 将 React 相关库单独打包
"react-vendor": ["react", "react-dom"],
// 其他大型依赖也可以单独打包
"chart-vendor": ["chart.js"],
// 将 ECharts 单独打包
"echarts-vendor": ["echarts"],
// 将其他组件打包到一起
components: ["./src/components"],
},
},
},
// 提高警告阈值,避免不必要的警告
chunkSizeWarningLimit: 1000,
},
},
integrations: [
// MDX 集成配置
mdx({
// 不使用共享的 markdown 配置
extendMarkdownConfig: false,
// 为 MDX 单独配置所需功能
remarkPlugins: [
// 添加表情符号支持
[remarkEmoji, { emoticon: false, padded: true }]
expressiveCode({
themes: ['github-light', 'dracula'],
themeCssSelector: (theme) =>
theme.name === 'dracula' ? '[data-theme=dark]' : '[data-theme=light]',
useDarkModeMediaQuery: false,
plugins: [
pluginLineNumbers(),
pluginCollapsibleSections(),
],
rehypePlugins: [
[rehypeExternalLinks, { target: '_blank', rel: ['nofollow', 'noopener', 'noreferrer'] }]
],
// 设置代码块处理行为
remarkRehype: {
allowDangerousHtml: false // 不解析 HTML
defaultProps: {
showLineNumbers: true,
collapseStyle: 'collapsible-auto',
},
frames: {
extractFileNameFromCode: true,
},
styleOverrides: {
// 核心样式设置
borderRadius: '0.5rem',
borderWidth: '0.5px',
codeFontSize: '0.9rem',
codeFontFamily: "'JetBrains Mono', Menlo, Monaco, Consolas, 'Courier New', monospace",
codeLineHeight: '1.5',
codePaddingInline: '1.5rem',
codePaddingBlock: '1.2rem',
// 框架样式设置
frames: {
shadowColor: 'rgba(0, 0, 0, 0.12)',
editorActiveTabBackground: '#ffffff',
editorTabBarBackground: '#f5f5f5',
terminalBackground: '#1a1a1a',
terminalTitlebarBackground: '#333333',
},
// 文本标记样式
textMarkers: {
defaultChroma: 'rgba(255, 255, 0, 0.2)',
},
},
gfm: true
}),
// MDX 集成配置
mdx(),
swup(),
react(),
pagefind(),
sitemap({
filter: (page) => !page.includes("/api/"),
serialize(item) {
@ -129,11 +140,13 @@ export default defineConfig({
}
},
}),
// 添加压缩插件 (必须放在最后位置)
compressor()
],
// Markdown 配置
markdown: {
syntaxHighlight: 'prism',
syntaxHighlight: false, // 禁用默认的语法高亮使用expressiveCode代替
remarkPlugins: [
[remarkEmoji, { emoticon: false, padded: true }]
],
@ -148,11 +161,6 @@ export default defineConfig({
// 确保代码块内容不被解析
passThrough: ['code']
},
shikiConfig: {
theme: "github-dark",
langs: [],
wrap: true,
},
},
adapter: vercel(),

1745
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,12 +14,18 @@
"@astrojs/react": "^4.2.4",
"@astrojs/sitemap": "^3.3.0",
"@astrojs/vercel": "^8.1.3",
"@astrolib/seo": "^1.0.0-beta.8",
"@expressive-code/plugin-collapsible-sections": "^0.41.2",
"@expressive-code/plugin-line-numbers": "^0.41.2",
"@swup/astro": "^1.6.0",
"@tailwindcss/vite": "^4.1.4",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"@types/three": "^0.174.0",
"astro": "^5.7.4",
"astro-expressive-code": "^0.41.2",
"astro-pagefind": "^1.8.3",
"astro-theme-toggle": "^0.6.0",
"cheerio": "^1.0.0",
"node-fetch": "^3.3.2",
"octokit": "^3.2.1",
@ -31,6 +37,7 @@
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.16",
"astro-compressor": "^1.0.0",
"rehype-external-links": "^3.0.0",
"remark-emoji": "^5.0.1"
}

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
---
import { SITE_NAME, NAV_LINKS } from "@/consts.ts";
import ThemeToggle from "./ThemeToggle.astro";
import { Toggle } from "astro-theme-toggle";
import Search from "astro-pagefind/components/Search";
// 获取当前路径
const currentPath = Astro.url.pathname;
@ -41,52 +42,24 @@ const normalizedPath =
<!-- 导航链接 -->
<div class="hidden md:flex md:items-center md:space-x-8">
<!-- 桌面端搜索框 -->
<div class="relative">
<input
type="text"
id="desktop-search"
class="w-48 pl-10 pr-4 py-1.5 rounded-full text-sm text-gray-700 dark:text-gray-200 placeholder-gray-500 dark:placeholder-gray-400 bg-gray-50/80 dark:bg-gray-800/60 border border-gray-200/60 dark:border-gray-700/40 focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-500 focus:bg-white dark:focus:bg-gray-800 focus:border-primary-300 dark:focus:border-primary-600"
placeholder="搜索文章..."
/>
<div
class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"
>
<svg
class="h-4 w-4 text-gray-400 dark:text-gray-500"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
></path>
</svg>
</div>
<!-- 搜索结果容器(默认隐藏) -->
<div
id="desktop-search-results"
class="absolute top-full left-0 right-0 mt-2 max-h-80 overflow-y-auto rounded-lg bg-white/95 dark:bg-gray-800/95 shadow-md border border-gray-200/70 dark:border-gray-700/70 backdrop-blur-sm z-50 hidden"
>
<!-- 结果将通过JS动态填充 -->
<div
class="p-4 text-center text-gray-500 dark:text-gray-400"
id="desktop-search-message"
>
<p>输入关键词开始搜索</p>
</div>
<ul
class="divide-y divide-gray-200/70 dark:divide-gray-700/70"
id="desktop-search-list"
>
</ul>
</div>
</div>
<Search
className="pagefind-ui"
uiOptions={{
showImages: false,
resetStyles: false,
showSubResults: true,
translations: {
placeholder: "搜索文章...",
clear_search: "清除",
load_more: "加载更多结果",
search_label: "搜索网站",
filters_label: "筛选",
zero_results: "未找到 [SEARCH_TERM] 的结果",
many_results: "找到 [COUNT] 个 [SEARCH_TERM] 的结果",
one_result: "找到 [COUNT] 个 [SEARCH_TERM] 的结果",
},
}}
/>
{
NAV_LINKS.map((link) => (
@ -103,7 +76,40 @@ const normalizedPath =
</a>
))
}
<ThemeToggle />
<div
class="inline-flex items-center justify-center cursor-pointer rounded-md hover:bg-gray-100 dark:hover:bg-gray-700/50 group relative mt-1.5 w-8 h-8"
>
<Toggle class="flex items-center justify-center h-full w-full">
<Fragment
slot="icon-light"
class="flex items-center justify-center h-full w-full"
>
<svg
viewBox="0 0 16 16"
class="w-4 h-4 text-gray-900 dark:text-white group-hover:text-primary-600 dark:group-hover:text-primary-400 fill-current flex-shrink-0 m-auto absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
aria-hidden="true"
>
<path
d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"
></path>
</svg>
</Fragment>
<Fragment
slot="icon-dark"
class="flex items-center justify-center h-full w-full"
>
<svg
viewBox="0 0 16 16"
class="w-4 h-4 text-gray-900 dark:text-white group-hover:text-primary-600 dark:group-hover:text-primary-400 fill-current flex-shrink-0 m-auto absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
aria-hidden="true"
>
<path
d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"
></path>
</svg>
</Fragment>
</Toggle>
</div>
</div>
<!-- 移动端菜单按钮 -->
@ -181,70 +187,25 @@ const normalizedPath =
id="mobile-search-panel"
class="hidden md:hidden fixed inset-x-0 top-16 p-4 bg-white dark:bg-gray-800 shadow-md z-50 border-t border-gray-200 dark:border-gray-700"
>
<div class="relative">
<input
type="text"
id="mobile-search"
class="w-full pl-10 pr-10 py-2 rounded-full text-sm text-gray-700 dark:text-gray-200 placeholder-gray-500 dark:placeholder-gray-400 bg-gray-50/80 dark:bg-gray-800/60 border border-gray-200/60 dark:border-gray-700/40 focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-500 focus:bg-white dark:focus:bg-gray-800 focus:border-primary-300 dark:focus:border-primary-600"
placeholder="搜索文章..."
<div>
<Search
className="pagefind-ui"
uiOptions={{
showImages: false,
resetStyles: false,
showSubResults: true,
translations: {
placeholder: "搜索文章...",
clear_search: "清除",
load_more: "加载更多结果",
search_label: "搜索网站",
filters_label: "筛选",
zero_results: "未找到 [SEARCH_TERM] 的结果",
many_results: "找到 [COUNT] 个 [SEARCH_TERM] 的结果",
one_result: "找到 [COUNT] 个 [SEARCH_TERM] 的结果",
},
}}
/>
<div
class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"
>
<svg
class="h-5 w-5 text-gray-400 dark:text-gray-500"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
></path>
</svg>
</div>
<button
id="mobile-search-close"
class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
aria-label="关闭搜索"
>
<svg
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
></path>
</svg>
</button>
</div>
<!-- 移动端搜索结果 -->
<div
id="mobile-search-results"
class="mt-3 max-h-80 overflow-y-auto rounded-lg bg-white/95 dark:bg-gray-800/95 shadow-md border border-gray-200/70 dark:border-gray-700/70 backdrop-blur-sm hidden"
>
<!-- 结果将通过JS动态填充 -->
<div
class="p-4 text-center text-gray-500 dark:text-gray-400"
id="mobile-search-message"
>
<p>输入关键词开始搜索</p>
</div>
<ul
class="divide-y divide-gray-200/70 dark:divide-gray-700/70"
id="mobile-search-list"
>
</ul>
</div>
</div>
@ -281,7 +242,40 @@ const normalizedPath =
<span class="text-sm font-medium text-gray-600 dark:text-gray-300"
>切换主题</span
>
<ThemeToggle />
<div
class="group relative w-7 h-7 mt-1 flex items-center justify-center"
>
<Toggle class="flex items-center justify-center h-full w-full">
<Fragment
slot="icon-light"
class="flex items-center justify-center h-full w-full"
>
<svg
viewBox="0 0 16 16"
class="w-3.5 h-3.5 text-gray-900 dark:text-white group-hover:text-primary-600 dark:group-hover:text-primary-400 fill-current flex-shrink-0 m-auto absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
aria-hidden="true"
>
<path
d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"
></path>
</svg>
</Fragment>
<Fragment
slot="icon-dark"
class="flex items-center justify-center h-full w-full"
>
<svg
viewBox="0 0 16 16"
class="w-3.5 h-3.5 text-gray-900 dark:text-white group-hover:text-primary-600 dark:group-hover:text-primary-400 fill-current flex-shrink-0 m-auto absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
aria-hidden="true"
>
<path
d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"
></path>
</svg>
</Fragment>
</Toggle>
</div>
</div>
</div>
</div>
@ -300,20 +294,6 @@ const normalizedPath =
handler: EventListenerOrEventListenerObject;
}> = [];
// 记录是否已加载文章数据
interface Article {
id: string;
title: string;
date: string | Date;
summary?: string;
tags?: string[];
image?: string;
content?: string;
}
let articles: Article[] = [];
let isArticlesLoaded = false;
// 添加事件监听器并记录,方便后续统一清理
function addListener<K extends keyof HTMLElementEventMap>(
element: EventTarget | null,
@ -437,7 +417,6 @@ const normalizedPath =
"mobile-search-button",
);
const mobileSearchPanel = document.getElementById("mobile-search-panel");
const mobileSearch = document.getElementById("mobile-search");
const mobileSearchClose = document.getElementById("mobile-search-close");
// 关闭移动端菜单的函数
@ -457,6 +436,130 @@ const normalizedPath =
}
}
// 清空桌面搜索框的函数
function clearDesktopSearch(): void {
// 获取关键元素
const searchInput = document.querySelector('.pagefind-ui__search-input');
const clearButton = document.querySelector('.pagefind-ui__search-clear');
const resultsContainer = document.querySelector('.pagefind-ui__results');
// 隐藏搜索结果面板
if (resultsContainer) {
// 直接隐藏结果容器
resultsContainer.setAttribute('style', 'display: none !important');
}
// 如果有清除按钮,点击它清空输入框
if (clearButton) {
(clearButton as HTMLElement).click();
} else if (searchInput) {
// 备选方案,设置输入框为空
(searchInput as HTMLInputElement).value = '';
searchInput.dispatchEvent(new Event('input', { bubbles: true }));
}
}
// 监听搜索结果链接点击事件
function setupSearchResultListeners(): void {
// 使用事件委托,监听整个搜索容器
const searchContainer = document.querySelector(".pagefind-ui");
if (searchContainer) {
// 移除之前可能存在的事件监听器
const existingHandler = (searchContainer as any)._clickHandler;
if (existingHandler) {
searchContainer.removeEventListener("click", existingHandler);
}
// 添加新的事件监听器
const clickHandler = (e: Event) => {
const target = e.target as HTMLElement;
// 检查是否点击了搜索结果链接或其父元素
const linkElement = target.closest(
".pagefind-ui__result-link, .pagefind-ui__result-title, .pagefind-ui__results a",
);
if (linkElement && linkElement.tagName === "A") {
// 不做任何处理,避免干扰导航
// 让路由变化后的处理函数来隐藏搜索面板
}
};
// 存储处理函数以便后续可能的移除
(searchContainer as any)._clickHandler = clickHandler;
// 添加事件监听
addListener(searchContainer, "click", clickHandler, {
capture: true,
});
} else {
console.warn("[Header] 未找到搜索容器,无法添加点击事件");
}
// 直接为文档添加事件监听,确保能捕获到所有点击
addListener(
document.body,
"click",
(e) => {
const target = e.target as HTMLElement;
const isSearchResult = target.closest(
".pagefind-ui__result-link, .pagefind-ui__result-title, .pagefind-ui__results a",
);
if (isSearchResult && isSearchResult.tagName === "A") {
// 不做任何处理,避免干扰导航
// 让路由变化后的处理函数来隐藏搜索面板
// 只关闭移动端搜索面板,因为它不影响导航
closeMobileSearch();
}
},
{ capture: true },
);
}
// 初始化搜索功能并设置页面加载后的变化监听
function initSearch(): void {
setupSearchResultListeners();
// Pagefind可能在页面加载后动态添加内容添加MutationObserver以监视这些变化
const pageObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.addedNodes.length > 0) {
// 检查是否有新添加的搜索容器或结果
const hasNewSearchElements = Array.from(mutation.addedNodes).some(
(node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
const element = node as HTMLElement;
return (
element.classList?.contains("pagefind-ui") ||
element.querySelector(".pagefind-ui") !== null ||
element.classList?.contains("pagefind-ui__results") ||
element.querySelector(".pagefind-ui__results") !== null
);
}
return false;
},
);
if (hasNewSearchElements) {
setupSearchResultListeners();
}
}
}
});
// 观察整个body以捕获所有变化
pageObserver.observe(document.body, { childList: true, subtree: true });
// 添加导航后清空搜索框的监听
addListener(document, "astro:page-load", () => {
// 页面加载后清空搜索框和隐藏结果面板
setTimeout(() => {
clearDesktopSearch();
}, 100); // 短暂延迟确保搜索组件已加载
});
}
if (mobileMenuButton && mobileMenu && menuOpenIcon && menuCloseIcon) {
// 移动端菜单按钮点击事件
(mobileMenuButton as HTMLElement).style.pointerEvents = "auto";
@ -526,7 +629,6 @@ const normalizedPath =
} else {
closeMobileMenu();
mobileSearchPanel.classList.remove("hidden");
if (mobileSearch) mobileSearch.focus();
}
},
{ capture: true },
@ -548,6 +650,9 @@ const normalizedPath =
}
}
// 初始化搜索功能
initSearch();
// 处理移动端主题切换容器
const themeToggleContainer = document.getElementById(
"theme-toggle-container",
@ -560,13 +665,12 @@ const normalizedPath =
(e: Event) => {
const target = e.target as HTMLElement;
if (
target.id !== "theme-toggle-button" &&
!target.closest("#theme-toggle-button")
target.tagName !== "ASTRO-THEME-TOGGLE" &&
!target.closest("astro-theme-toggle")
) {
e.stopPropagation();
const toggleButton = themeToggleContainer.querySelector(
"#theme-toggle-button",
);
const toggleButton =
themeToggleContainer.querySelector("astro-theme-toggle");
if (toggleButton) {
(toggleButton as HTMLElement).click();
}
@ -575,309 +679,47 @@ const normalizedPath =
{ capture: true },
);
}
}
// 搜索功能逻辑
function initSearch(): void {
// 搜索节流函数
function debounce<T extends (...args: any[]) => void>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeout: ReturnType<typeof setTimeout> | undefined;
return function(this: any, ...args: Parameters<T>): void {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
// 获取DOM元素
const desktopSearch = document.getElementById("desktop-search");
const desktopResults = document.getElementById("desktop-search-results");
const desktopList = document.getElementById("desktop-search-list");
const desktopMessage = document.getElementById("desktop-search-message");
const mobileSearch = document.getElementById("mobile-search");
const mobileResults = document.getElementById("mobile-search-results");
const mobileList = document.getElementById("mobile-search-list");
const mobileMessage = document.getElementById("mobile-search-message");
// 获取文章数据
async function fetchArticles(): Promise<void> {
if (isArticlesLoaded && articles.length > 0) return;
try {
const response = await fetch("/api/search");
if (!response.ok) {
throw new Error("获取文章数据失败");
}
articles = await response.json();
isArticlesLoaded = true;
} catch (error) {
console.error("获取文章失败:", error);
}
}
// 高亮文本中的匹配部分
function highlightText(text: string, query: string): string {
if (!text || !query.trim()) return text;
const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const regex = new RegExp(`(${escapedQuery})`, "gi");
return text.replace(
regex,
'<mark class="bg-yellow-100 dark:bg-yellow-900/30 text-gray-900 dark:text-gray-100 px-0.5 rounded">$1</mark>',
);
}
// 搜索文章
function searchArticles(
query: string,
resultsList: HTMLElement,
resultsMessage: HTMLElement
): void {
if (!query.trim()) {
resultsList.innerHTML = "";
resultsMessage.textContent = "输入关键词开始搜索";
resultsMessage.style.display = "block";
return;
}
if (articles.length === 0) {
resultsMessage.textContent = "正在加载数据...";
resultsMessage.style.display = "block";
return;
}
const lowerQuery = query.toLowerCase();
// 过滤并排序结果
const filteredArticles = articles
.filter((article: Article) => {
const title = article.title.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: string) => tag.includes(lowerQuery)) ||
summary.includes(lowerQuery) ||
content.includes(lowerQuery)
);
})
.sort((a: Article, b: Article) => {
// 标题匹配优先
const aTitle = a.title.toLowerCase();
const bTitle = b.title.toLowerCase();
if (aTitle.includes(lowerQuery) && !bTitle.includes(lowerQuery)) {
return -1;
}
if (!aTitle.includes(lowerQuery) && bTitle.includes(lowerQuery)) {
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();
})
.slice(0, 10); // 限制结果数量
if (filteredArticles.length === 0) {
resultsList.innerHTML = "";
resultsMessage.textContent = "没有找到相关内容";
resultsMessage.style.display = "block";
return;
}
// 显示结果
resultsMessage.style.display = "none";
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 = `<p class="text-xs text-gray-500 dark:text-gray-400 mt-1 line-clamp-2">${snippet}</p>`;
}
// 高亮标题和摘要中的匹配文本
const highlightedTitle = highlightText(article.title, query);
const highlightedSummary = article.summary
? highlightText(article.summary, query)
: "";
return `
<li>
<a href="/articles/${article.id}" class="block px-4 py-3 hover:bg-gray-100 dark:hover:bg-gray-700/70 search-result-link">
<h3 class="text-sm font-medium text-gray-800 dark:text-gray-200 truncate">${highlightedTitle}</h3>
${article.summary ? `<p class="text-xs text-gray-500 dark:text-gray-400 mt-1 truncate">${highlightedSummary}</p>` : ""}
${contentMatch}
${
article.tags && article.tags.length > 0
? `
<div class="flex flex-wrap gap-1 mt-1.5">
${article.tags
.slice(0, 3)
.map(
(tag: string) => `
<span class="inline-block text-xs bg-primary-50/50 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400 py-0.5 px-1.5 rounded-full">#${tag}</span>
`,
)
.join("")}
${article.tags.length > 3 ? `<span class="text-xs text-gray-400 dark:text-gray-500">+${article.tags.length - 3}</span>` : ""}
</div>
`
: ""
}
</a>
</li>
`;
})
.join("");
// 路由变化时处理
const routeEvents = [
"astro:page-load", // Astro路由导航完成
"astro:after-swap" // Astro视图变化后
];
routeEvents.forEach(eventName => {
addListener(document, eventName, () => {
// 页面加载后清空搜索框和隐藏结果面板
clearDesktopSearch();
closeMobileSearch();
});
});
// 直接监听popstate和pushstate事件
addListener(window, "popstate", () => {
clearDesktopSearch();
closeMobileSearch();
});
// 添加自定义监听处理history API
const originalPushState = history.pushState;
history.pushState = function(...args) {
// 调用原始方法
const result = originalPushState.apply(this, args as [any, string, string | URL | null]);
// 为搜索结果链接添加点击事件,点击后关闭搜索面板
addSearchResultsClickListeners();
}
// 节流搜索
const debouncedDesktopSearch = debounce((value: string) => {
if (desktopList && desktopMessage) {
searchArticles(
value,
desktopList as HTMLElement,
desktopMessage as HTMLElement,
);
}
}, 300);
const debouncedMobileSearch = debounce((value: string) => {
if (mobileList && mobileMessage) {
searchArticles(
value,
mobileList as HTMLElement,
mobileMessage as HTMLElement,
);
}
}, 300);
// 桌面端搜索逻辑
if (desktopSearch && desktopResults) {
// 聚焦时显示结果
addListener(desktopSearch, "focus", () => {
desktopResults.classList.remove("hidden");
if (!isArticlesLoaded) fetchArticles();
});
// 输入时更新搜索结果
addListener(desktopSearch, "input", (e: Event) => {
const target = e.target as HTMLInputElement;
if (target && target.value !== undefined) {
debouncedDesktopSearch(target.value);
}
});
// 点击外部关闭结果
const documentClickHandler = (e: MouseEvent) => {
const target = e.target as Node;
if (
desktopSearch &&
!desktopSearch.contains(target) &&
!desktopResults.contains(target)
) {
desktopResults.classList.add("hidden");
}
};
addListener(document, "click", documentClickHandler as EventListener);
// ESC键关闭结果
addListener(desktopSearch, "keydown", ((e: KeyboardEvent) => {
if (e.key === "Escape") {
desktopResults.classList.add("hidden");
}
}) as EventListener);
}
// 移动端搜索逻辑
if (mobileSearch && mobileResults) {
// 输入时更新搜索结果
addListener(mobileSearch, "input", (e: Event) => {
mobileResults.classList.remove("hidden");
const target = e.target as HTMLInputElement;
if (target && target.value !== undefined) {
debouncedMobileSearch(target.value);
if (!isArticlesLoaded) fetchArticles();
}
});
// ESC键关闭搜索面板
addListener(mobileSearch, "keydown", ((e: KeyboardEvent) => {
if (e.key === "Escape") {
const mobileSearchPanel = document.getElementById(
"mobile-search-panel",
);
if (mobileSearchPanel) {
mobileSearchPanel.classList.add("hidden");
}
}
}) as EventListener);
}
}
// 关闭所有搜索面板的函数
function closeSearchPanels(): void {
const desktopSearchResults = document.getElementById("desktop-search-results");
if (desktopSearchResults) {
desktopSearchResults.classList.add("hidden");
}
// 触发自定义事件
const event = new Event("pushstate");
window.dispatchEvent(event);
// 在pushstate时清空搜索面板
clearDesktopSearch();
closeMobileSearch();
return result;
};
const mobileSearchPanel = document.getElementById("mobile-search-panel");
if (mobileSearchPanel) {
mobileSearchPanel.classList.add("hidden");
}
}
// 为搜索结果链接添加点击事件
function addSearchResultsClickListeners(): void {
const searchResultLinks = document.querySelectorAll('.search-result-link');
searchResultLinks.forEach(link => {
addListener(link, 'click', () => {
closeSearchPanels();
});
addListener(window, "pushstate", () => {
clearDesktopSearch();
closeMobileSearch();
});
}
@ -897,12 +739,7 @@ const normalizedPath =
function setupHeader(): void {
cleanup();
initHeader();
initSearch();
registerCleanup();
// 在页面路由变化时关闭搜索面板
addListener(document, 'astro:page-load', closeSearchPanels);
addListener(document, 'astro:after-swap', closeSearchPanels);
}
// 在页面加载时初始化

View File

@ -3,6 +3,8 @@ import "@/styles/global.css";
import Header from "@/components/Header.astro";
import Footer from "@/components/Footer.astro";
import { ICP, PSB_ICP, PSB_ICP_URL, SITE_NAME, SITE_DESCRIPTION } from "@/consts";
import { ThemeScript } from 'astro-theme-toggle';
import { AstroSeo } from '@astrolib/seo';
// 定义Props接口
interface Props {
@ -27,72 +29,41 @@ const { title = SITE_NAME, description = SITE_DESCRIPTION, date, tags } = Astro.
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<!-- 基本元数据 -->
<title>{title}</title>
<meta name="description" content={description || `${SITE_NAME} - 个人博客`} />
<link rel="canonical" href={canonicalURL} />
<!-- 使用 AstroSeo 组件替换原有的 SEO 标签 -->
<AstroSeo
title={title}
description={description || `${SITE_NAME} - 个人博客`}
canonical={canonicalURL.toString()}
openGraph={{
type: 'article',
url: canonicalURL.toString(),
title: title,
description: description || `${SITE_NAME} - 个人博客`,
site_name: SITE_NAME,
...(date && { article: {
publishedTime: date.toISOString(),
tags: tags || []
}}),
}}
twitter={{
cardType: 'summary_large_image',
site: SITE_NAME,
handle: SITE_NAME,
}}
additionalMetaTags={[
{
property: 'article:published_time',
content: date ? date.toISOString() : '',
},
...(tags?.map(tag => ({
property: 'article:tag',
content: tag,
})) || []),
]}
/>
<!-- Open Graph / Facebook -->
<meta property="og:type" content="article" />
<meta property="og:url" content={canonicalURL} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description || `${SITE_NAME} - 个人博客`} />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={canonicalURL} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description || `${SITE_NAME} - 个人博客`} />
<!-- 文章特定元数据 -->
{date && <meta property="article:published_time" content={date.toISOString()} />}
{tags && tags.map(tag => (
<meta property="article:tag" content={tag} />
))}
<script is:inline>
// 立即执行主题初始化,采用"无闪烁"加载方式
(function() {
try {
// 获取系统首选主题
const getSystemTheme = () => {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
};
const storedTheme = typeof localStorage !== 'undefined' ? localStorage.getItem('theme') : null;
const systemTheme = getSystemTheme();
let theme = 'light'; // 默认浅色主题
// 按照逻辑优先级应用主题
if (storedTheme) {
// 如果有存储的主题设置,则应用它
theme = storedTheme;
} else if (systemTheme) {
// 如果没有存储的设置,检查系统主题
theme = systemTheme;
}
// 立即设置文档主题在DOM渲染前应用避免闪烁
document.documentElement.dataset.theme = theme;
// 监听系统主题变化(只有当主题设为跟随系统时才响应)
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleMediaChange = (e) => {
// 只有当主题设置为跟随系统时才更新主题
if (!localStorage.getItem('theme')) {
const newTheme = e.matches ? 'dark' : 'light';
document.documentElement.dataset.theme = newTheme;
}
};
// 添加系统主题变化监听
mediaQuery.addEventListener('change', handleMediaChange);
} catch (error) {
// 出错时应用默认浅色主题,确保页面正常显示
document.documentElement.dataset.theme = 'light';
}
})();
</script>
<!-- 主题切换脚本 -->
<ThemeScript />
</head>
<body class="m-0 w-full h-full bg-gray-50 dark:bg-dark-bg flex flex-col min-h-screen">
<Header />

View File

@ -1,434 +0,0 @@
---
interface Props {
height?: number;
width?: number;
fill?: string;
className?: string;
}
const {
height = 16,
width = 16,
fill = "currentColor",
className = ""
} = Astro.props;
---
<button
id="theme-toggle-button"
class={`inline-flex items-center justify-center h-8 w-8 cursor-pointer rounded-md hover:bg-gray-100 dark:hover:bg-gray-700/50 text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 ${className}`}
aria-label="切换主题"
role="button"
tabindex="0"
>
<!-- 月亮图标 (暗色模式) -->
<svg
id="dark-icon"
style={`height: ${height}px; width: ${width}px;`}
fill={fill}
viewBox="0 0 16 16"
class="hover:scale-110 hidden dark:block"
aria-hidden="true"
>
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"/>
</svg>
<!-- 太阳图标 (亮色模式) -->
<svg
id="light-icon"
style={`height: ${height}px; width: ${width}px;`}
fill={fill}
viewBox="0 0 16 16"
class="hover:scale-110 block dark:hidden"
aria-hidden="true"
>
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"/>
</svg>
</button>
<script is:inline>
// 主题切换逻辑
(function() {
// 页面导航计数器(跟踪页面跳转次数)
let pageNavigationCount = 0;
// 存储事件监听器,便于统一清理
const listeners = [];
// 定时器
let transitionTimeout = null;
// 直接从按钮移除事件监听器
function cleanupButtonListeners() {
// 查找所有主题切换按钮
const buttons = document.querySelectorAll('#theme-toggle-button');
buttons.forEach(button => {
// 移除所有可能的事件
if (button._clickHandler) {
button.removeEventListener('click', button._clickHandler, { capture: true });
delete button._clickHandler;
}
if (button._keydownHandler) {
button.removeEventListener('keydown', button._keydownHandler);
delete button._keydownHandler;
}
// 清除其他可能的事件
const otherClickHandlers = button.__themeToggleClickHandlers || [];
otherClickHandlers.forEach(handler => {
try {
button.removeEventListener('click', handler, { capture: true });
} catch (e) {
// 忽略错误
}
});
const otherKeydownHandlers = button.__themeToggleKeydownHandlers || [];
otherKeydownHandlers.forEach(handler => {
try {
button.removeEventListener('keydown', handler);
} catch (e) {
// 忽略错误
}
});
// 重置处理函数数组
button.__themeToggleClickHandlers = [];
button.__themeToggleKeydownHandlers = [];
});
// 清理容器
const container = document.getElementById('theme-toggle-container');
if (container) {
if (container._clickHandler) {
container.removeEventListener('click', container._clickHandler);
delete container._clickHandler;
}
// 清除其他可能的事件
const otherClickHandlers = container.__themeToggleClickHandlers || [];
otherClickHandlers.forEach(handler => {
try {
container.removeEventListener('click', handler);
} catch (e) {
// 忽略错误
}
});
// 重置处理函数数组
container.__themeToggleClickHandlers = [];
}
}
// 添加事件监听器并记录,方便后续统一清理
function addListener(element, eventType, handler, options) {
if (!element) return null;
// 确保先移除可能已存在的同类型事件处理函数
if (eventType === 'click' && element.id === 'theme-toggle-button') {
if (element._clickHandler) {
element.removeEventListener('click', element._clickHandler, { capture: true });
}
element._clickHandler = handler;
// 保存到数组中以便清理
if (!element.__themeToggleClickHandlers) {
element.__themeToggleClickHandlers = [];
}
element.__themeToggleClickHandlers.push(handler);
}
if (eventType === 'keydown' && element.id === 'theme-toggle-button') {
if (element._keydownHandler) {
element.removeEventListener('keydown', element._keydownHandler);
}
element._keydownHandler = handler;
// 保存到数组中以便清理
if (!element.__themeToggleKeydownHandlers) {
element.__themeToggleKeydownHandlers = [];
}
element.__themeToggleKeydownHandlers.push(handler);
}
if (eventType === 'click' && element.id === 'theme-toggle-container') {
if (element._clickHandler) {
element.removeEventListener('click', element._clickHandler);
}
element._clickHandler = handler;
// 保存到数组中以便清理
if (!element.__themeToggleClickHandlers) {
element.__themeToggleClickHandlers = [];
}
element.__themeToggleClickHandlers.push(handler);
}
element.addEventListener(eventType, handler, options);
listeners.push({ element, eventType, handler, options });
return handler;
}
// 清理函数 - 移除所有事件监听器
function cleanup() {
// 先直接从按钮清理事件
cleanupButtonListeners();
// 移除所有监听器
listeners.forEach(({ element, eventType, handler, options }) => {
try {
element.removeEventListener(eventType, handler, options);
} catch (err) {
// 忽略错误
}
});
// 清空数组
listeners.length = 0;
// 清理任何定时器
if (transitionTimeout) {
clearTimeout(transitionTimeout);
transitionTimeout = null;
}
}
// 初始化主题切换功能
function setupThemeToggle() {
// 确保当前没有活动的主题切换按钮事件
cleanup();
// 获取所有主题切换按钮
const themeToggleButtons = document.querySelectorAll('#theme-toggle-button');
if (!themeToggleButtons.length) {
return;
}
let transitioning = false;
// 获取系统首选主题
const getSystemTheme = () => {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
};
// 初始化主题
const initializeTheme = () => {
const storedTheme = localStorage.getItem('theme');
const systemTheme = getSystemTheme();
// 按照逻辑优先级应用主题
if (storedTheme) {
document.documentElement.dataset.theme = storedTheme;
} else if (systemTheme) {
document.documentElement.dataset.theme = systemTheme;
} else {
document.documentElement.dataset.theme = 'light';
}
};
// 切换主题
const toggleTheme = () => {
if (transitioning) {
return;
}
transitioning = true;
// 获取当前主题
const currentTheme = document.documentElement.dataset.theme;
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
// 更新 HTML 属性
document.documentElement.dataset.theme = newTheme;
// 更新本地存储
const systemTheme = getSystemTheme();
if (newTheme === systemTheme) {
localStorage.removeItem('theme');
} else {
localStorage.setItem('theme', newTheme);
}
// 添加防抖
if (transitionTimeout) {
clearTimeout(transitionTimeout);
}
transitionTimeout = setTimeout(() => {
transitioning = false;
}, 300);
};
// 监听系统主题变化
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleMediaChange = (e) => {
if (!localStorage.getItem('theme')) {
const newTheme = e.matches ? 'dark' : 'light';
document.documentElement.dataset.theme = newTheme;
}
};
// 添加系统主题变化监听
addListener(mediaQuery, 'change', handleMediaChange);
// 为每个按钮添加事件
themeToggleButtons.forEach((button, index) => {
// 确保移除旧的事件监听
if (button._clickHandler) {
button.removeEventListener('click', button._clickHandler, { capture: true });
}
if (button._keydownHandler) {
button.removeEventListener('keydown', button._keydownHandler);
}
try {
button.style.pointerEvents = 'auto';
} catch (e) {
// 忽略样式错误
}
// 创建点击处理函数
const clickHandler = (e) => {
e.preventDefault();
e.stopPropagation();
toggleTheme();
};
// 点击事件 - 使用捕获模式并保存引用
addListener(button, 'click', clickHandler, { capture: true });
// 键盘事件
const keydownHandler = (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggleTheme();
}
};
addListener(button, 'keydown', keydownHandler);
});
// 处理移动端主题切换容器
const themeToggleContainer = document.getElementById('theme-toggle-container');
if (themeToggleContainer) {
// 确保移除旧的事件监听
if (themeToggleContainer._clickHandler) {
themeToggleContainer.removeEventListener('click', themeToggleContainer._clickHandler);
}
const containerClickHandler = (e) => {
const target = e.target;
if (target.id !== 'theme-toggle-button' && !target.closest('#theme-toggle-button')) {
e.stopPropagation();
toggleTheme();
}
};
addListener(themeToggleContainer, 'click', containerClickHandler);
}
// 初始化主题
initializeTheme();
}
// 注册清理函数 - 确保在每次页面转换前清理事件
function registerCleanup() {
const cleanupEvents = [
'astro:before-preparation',
'astro:before-swap',
'swup:willReplaceContent'
];
// 为每个事件注册一次性清理函数
cleanupEvents.forEach(eventName => {
const handler = () => {
cleanup();
};
document.addEventListener(eventName, handler, { once: true });
});
// 页面卸载时清理
window.addEventListener('beforeunload', () => {
cleanup();
}, { once: true });
}
// 初始化函数
function init() {
pageNavigationCount++;
setupThemeToggle();
registerCleanup();
}
// 监听页面转换事件
function setupPageTransitionEvents() {
// 确保事件处理程序唯一性的函数
function setupUniqueEvent(eventName, callback) {
const eventKey = `__theme_toggle_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('#theme-toggle-button');
if (buttons.length > 0) {
cleanupButtonListeners();
setupThemeToggle();
}
}, 50);
}
});
}
// 设置页面转换事件监听
setupPageTransitionEvents();
// 在页面加载后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
init();
}, { once: true });
} else {
setTimeout(() => {
init();
}, 0);
}
})();
</script>

View File

@ -4,8 +4,8 @@ export const SITE_DESCRIPTION = "记录生活,分享所思";
export const NAV_LINKS = [
{ href: '/', text: '首页' },
{ href: '/articles', text: '文章' },
{ href: '/filtered', text: '筛选' },
{ href: '/articles', text: '文章' },
{ href: '/movies', text: '观影' },
{ href: '/books', text: '读书' },
{ href: '/projects', text: '项目' },

View File

@ -582,75 +582,11 @@ function getArticleUrl(articleId: string) {
updateActiveHeading();
}
function setupCodeBlocks() {
const codeBlocks = document.querySelectorAll("pre");
if (!codeBlocks.length) return;
codeBlocks.forEach(pre => {
const code = pre.querySelector("code");
if (!code || pre.querySelector('.code-header')) return;
const className = code.className;
const languageMatch = className.match(/language-(\w+)/);
const language = languageMatch ? languageMatch[1] : "text";
const header = document.createElement("div");
header.className = "code-header flex justify-between items-center text-xs px-4 py-2 bg-secondary-800 dark:bg-gray-900 text-secondary-300 dark:text-secondary-400 rounded-t-lg";
const languageLabel = document.createElement("span");
languageLabel.className = "code-language font-mono";
languageLabel.textContent = language;
const copyButton = document.createElement("button");
copyButton.className = "code-copy-button flex items-center gap-1 hover:text-white dark:hover:text-primary-400 transition-colors";
const copyIcon = `<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"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`;
const successIcon = `<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"><path d="M20 6L9 17l-5-5"></path></svg>`;
const errorIcon = `<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><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>`;
copyButton.innerHTML = `${copyIcon}<span>复制</span>`;
copyButton.setAttribute("aria-label", "复制代码");
copyButton.setAttribute("title", "复制代码到剪贴板");
addListener(copyButton, "click", (e) => {
e.stopPropagation();
navigator.clipboard.writeText(code.textContent || "")
.then(() => {
copyButton.innerHTML = `${successIcon}<span>已复制</span>`;
copyButton.classList.add("text-green-400");
setTimeout(() => {
copyButton.innerHTML = `${copyIcon}<span>复制</span>`;
copyButton.classList.remove("text-green-400");
}, 2000);
})
.catch(() => {
copyButton.innerHTML = `${errorIcon}<span>失败</span>`;
copyButton.classList.add("text-red-400");
setTimeout(() => {
copyButton.innerHTML = `${copyIcon}<span>复制</span>`;
copyButton.classList.remove("text-red-400");
}, 2000);
});
});
header.appendChild(languageLabel);
header.appendChild(copyButton);
pre.insertBefore(header, pre.firstChild);
pre.classList.add("rounded-b-lg", "mt-0");
pre.style.marginTop = "0";
});
}
function init() {
if (!document.querySelector("article")) return;
setupProgressBar();
setupTableOfContents();
setupCodeBlocks();
}
if (document.readyState === "loading") {

View File

@ -309,12 +309,12 @@ function getArticleUrl(articleId: string) {
</div>
<!-- 无结果提示 -->
<div id="noResultsMessage" class="hidden text-center py-16 bg-white rounded-xl shadow-lg border border-gray-200 dark:border-gray-700 mb-12">
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 mx-auto text-primary-200 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<div id="noResultsMessage" class="hidden text-center py-16 bg-white dark:bg-gray-800 rounded-xl shadow-lg border border-gray-200 dark:border-gray-700 mb-12">
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 mx-auto text-primary-200 dark:text-primary-700 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
</svg>
<h3 class="text-2xl font-bold text-gray-700 mb-2">没有找到符合条件的文章</h3>
<p class="text-gray-500 max-w-md mx-auto">请尝试调整筛选条件或者清除筛选条件</p>
<h3 class="text-2xl font-bold text-gray-700 dark:text-gray-200 mb-2">没有找到符合条件的文章</h3>
<p class="text-gray-500 dark:text-gray-400 max-w-md mx-auto">请尝试调整筛选条件或者清除筛选条件</p>
</div>
<!-- 筛选功能的客户端脚本 -->

1
src/pages/table-test.md Normal file
View File

@ -0,0 +1 @@

View File

@ -1,175 +0,0 @@
/* 代码块容器样式 */
.prose pre {
position: relative;
margin: 1.5em 0;
padding: 0;
border-radius: 0.5rem;
/* 调整背景色和边框 */
background-color: #282c34 !important;
border: 1px solid #374151;
overflow: hidden;
overflow-x: auto;
white-space: pre-wrap;
word-wrap: break-word;
}
/* 代码块顶部栏 - 调整颜色使其更突出 */
.code-header {
display: flex;
justify-content: space-between;
align-items: center;
/* 修改背景色为更深的颜色 */
background-color: #21252b;
padding: 0.5rem 1rem;
/* 调整边框颜色 */
border-bottom: 1px solid #374151;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
/* 语言标签 */
.code-language {
font-size: 0.75rem;
font-weight: 600;
/* 调整颜色使其更明显 */
color: #d1d5db;
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* 复制按钮 - 增强视觉效果 */
.code-copy-button {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 0.35rem 0.75rem;
font-size: 0.75rem;
/* 调整按钮颜色 */
color: #d1d5db;
background-color: #2c313a;
border: 1px solid #4b5563;
border-radius: 0.25rem;
cursor: pointer;
transition: all 0.2s ease;
}
.code-copy-button svg {
width: 14px;
height: 14px;
stroke: currentColor;
flex-shrink: 0;
}
.code-copy-button span {
font-size: 0.75rem;
font-weight: 500;
line-height: 1;
}
.code-copy-button:hover {
background-color: #374151;
color: #f3f4f6;
border-color: #6b7280;
}
.code-copy-button:active {
transform: scale(0.95);
}
/* 代码内容区域 */
.prose pre code {
display: block;
padding: 1.5em 1.25em;
overflow-x: auto;
font-family: "JetBrains Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 0.9em;
line-height: 1.6;
tab-size: 2;
/* 调整文字颜色 */
color: #abb2bf !important;
}
/* 自定义滚动条 */
.prose pre code::-webkit-scrollbar {
height: 8px;
background-color: #282c34;
}
.prose pre code::-webkit-scrollbar-thumb {
background-color: #4b5363;
border-radius: 4px;
}
.prose pre code::-webkit-scrollbar-thumb:hover {
background-color: #5a6377;
}
.prose pre code::-webkit-scrollbar-track {
background-color: #21252b;
border-radius: 4px;
}
/* Firefox滚动条 */
.prose pre code {
scrollbar-width: thin;
scrollbar-color: #4b5363 #282c34;
}
/* 代码行高亮 */
.prose pre .highlight-line {
background-color: rgba(171, 178, 191, 0.1);
display: block;
margin: 0 -1em;
padding: 0 1em;
}
/* 行号 */
.prose pre code {
counter-reset: line;
}
.prose pre code .line {
display: inline-block;
width: 100%;
}
/* 内联代码样式 */
.prose :not(pre) > code {
background-color: rgba(171, 178, 191, 0.1);
padding: 0.2em 0.4em;
border-radius: 0.25em;
font-weight: 400;
white-space: nowrap;
border: 1px solid rgba(171, 178, 191, 0.2);
font-family: "JetBrains Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 0.85em;
color: #e06c75;
white-space: pre-wrap;
word-wrap: break-word;
}
/* 暗色模式适配 */
[data-theme="dark"] .prose pre,
[data-theme="dark"] .code-header {
background-color: #282c34 !important;
border-color: #374151;
}
[data-theme="dark"] .code-copy-button {
background-color: #2c313a;
border-color: #4b5563;
color: #d1d5db;
}
[data-theme="dark"] .code-copy-button:hover {
background-color: #374151;
color: #f3f4f6;
border-color: #6b7280;
}
[data-theme="dark"] .prose pre code {
/* 调整暗色模式文字颜色 */
color: #abb2bf;
}

View File

@ -1,186 +1,3 @@
/* 增强表格样式 */
.prose table {
border-collapse: separate;
border-spacing: 0;
width: 100%;
margin: 1.5em 0;
border-radius: 0.75rem;
overflow: hidden;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
border: 2px solid var(--color-secondary-500);
}
/* 确保表格边框在所有浏览器中都能正确显示 */
.prose table * {
box-sizing: border-box;
}
.prose thead {
background-color: var(--color-primary-50);
}
.prose thead th {
padding: 1rem 1.25rem;
font-weight: 600;
text-align: left;
color: var(--color-primary-900);
font-size: 0.95rem;
line-height: 1.5rem;
white-space: nowrap;
position: relative;
vertical-align: middle;
border-bottom: 2px solid var(--color-secondary-500);
border-right: 2px solid var(--color-secondary-500);
}
.prose thead th:last-child {
border-right: none;
}
.prose tbody tr {
border-bottom: 2px solid var(--color-secondary-500);
}
.prose tbody tr:last-child {
border-bottom: none;
}
.prose tbody tr:hover {
background-color: var(--color-secondary-50);
}
.prose tbody td {
padding: 0.875rem 1.25rem;
color: var(--color-secondary-800);
font-size: 0.95rem;
line-height: 1.5rem;
vertical-align: middle;
position: relative;
border-right: 2px solid var(--color-secondary-500);
border-bottom: 2px solid var(--color-secondary-500);
}
.prose tbody td:last-child {
border-right: none;
}
.prose tbody tr:last-child td {
border-bottom: none;
}
.prose tbody tr:nth-child(even) {
background-color: var(--color-secondary-50/80);
}
.prose tbody tr:nth-child(even):hover {
background-color: var(--color-secondary-50);
}
/* 表格内容对齐样式 */
.prose th[align="center"],
.prose td[align="center"] {
text-align: center;
}
.prose th[align="right"],
.prose td[align="right"] {
text-align: right;
}
.prose th[align="left"],
.prose td[align="left"] {
text-align: left;
}
/* 表格内容样式增强 */
.prose td code {
background-color: var(--color-secondary-100);
padding: 0.2em 0.4em;
border-radius: 0.25em;
font-size: 0.875em;
}
[data-theme="dark"] .prose td code {
background-color: var(--color-secondary-800);
}
/* 表格内的链接样式 */
.prose td a {
color: var(--color-primary-600);
text-decoration: none;
border-bottom: 1px solid var(--color-primary-300);
transition: all 0.2s ease;
}
.prose td a:hover {
color: var(--color-primary-700);
border-bottom-color: var(--color-primary-500);
}
[data-theme="dark"] .prose td a {
color: var(--color-primary-400);
border-bottom-color: var(--color-primary-700);
}
[data-theme="dark"] .prose td a:hover {
color: var(--color-primary-300);
border-bottom-color: var(--color-primary-500);
}
/* 表格内复选框样式 */
.prose td input[type="checkbox"] {
width: 1.25em;
height: 1.25em;
margin: 0;
vertical-align: middle;
position: relative;
top: -1px;
accent-color: var(--color-primary-500);
}
/* 表格内图标样式 */
.prose td svg,
.prose td img:not(.emoji) {
vertical-align: middle;
display: inline-block;
width: auto;
height: 1.25em;
margin: 0 0.25em;
}
/* 表格内的状态标记 */
.prose td .status {
display: inline-flex;
align-items: center;
padding: 0.25em 0.75em;
border-radius: 9999px;
font-size: 0.75em;
font-weight: 500;
line-height: 1.5;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.prose td .status-success {
background-color: rgba(16, 185, 129, 0.1);
color: rgb(6, 95, 70);
}
.prose td .status-warning {
background-color: rgba(245, 158, 11, 0.1);
color: rgb(146, 64, 14);
}
.prose td .status-error {
background-color: rgba(239, 68, 68, 0.1);
color: rgb(153, 27, 27);
}
.prose td .status-info {
background-color: rgba(59, 130, 246, 0.1);
color: rgb(30, 64, 175);
}
/* 增强列表样式 */
.prose ul {
list-style-type: disc;
@ -432,206 +249,9 @@
border-bottom-color: var(--color-primary-400);
}
/* 响应式表格样式 */
/* 响应式样式 */
@media (max-width: 640px) {
.prose table {
display: block;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.prose thead th {
white-space: nowrap;
padding: 0.75rem 1rem;
}
.prose tbody td {
padding: 0.75rem 1rem;
}
}
/* 表格内容溢出处理 */
.prose td p {
margin: 0;
}
.prose td > *:first-child {
margin-top: 0;
}
.prose td > *:last-child {
margin-bottom: 0;
}
.prose td.truncate {
max-width: 20rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* 确保表格边框在所有情况下都能正确显示 */
.prose table tr td:last-child {
border-right: none !important;
}
.prose table tr th:last-child {
border-right: none !important;
}
.prose table tr:last-child td {
border-bottom: none !important;
}
/* 暗色模式下的边框颜色 */
[data-theme="dark"] .prose table {
--table-border-color: var(--color-dark-border);
}
/* 增强列表样式 */
.prose ul {
list-style-type: disc;
margin-top: 1.25em;
margin-bottom: 1.25em;
padding-left: 1.625em;
}
.prose ul li {
margin-top: 0.5em;
margin-bottom: 0.5em;
padding-left: 0.375em;
}
.prose ul li::marker {
color: #6b7280;
}
.prose ul li ul {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
.prose ol {
list-style-type: decimal;
margin-top: 1.25em;
margin-bottom: 1.25em;
padding-left: 1.625em;
}
.prose ol li {
margin-top: 0.5em;
margin-bottom: 0.5em;
padding-left: 0.375em;
}
.prose ol li::marker {
color: #6b7280;
}
/* 添加明确的表格边框样式 */
.prose table {
border: 2px solid var(--color-secondary-500) !important;
}
.prose th {
border-right: 2px solid var(--color-secondary-500) !important;
border-bottom: 2px solid var(--color-secondary-500) !important;
}
.prose td {
border-right: 2px solid var(--color-secondary-500) !important;
border-bottom: 2px solid var(--color-secondary-500) !important;
}
.prose th:last-child,
.prose td:last-child {
border-right: none !important;
}
.prose tr:last-child td {
border-bottom: none !important;
}
[data-theme="dark"] .prose th,
[data-theme="dark"] .prose td {
border-color: var(--color-dark-border) !important;
}
[data-theme="dark"] .prose table {
border-color: var(--color-dark-border) !important;
}
/* 暗色模式表格样式 */
[data-theme="dark"] .prose table {
border-color: var(--color-dark-border);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
border-radius: 0.75rem;
border-width: 2px;
}
[data-theme="dark"] .prose thead {
background-color: var(--color-dark-surface);
}
[data-theme="dark"] .prose thead th {
color: var(--color-primary-300);
border-bottom: 2px solid var(--color-dark-border);
border-right: 2px solid var(--color-dark-border);
font-weight: 600;
}
[data-theme="dark"] .prose thead th:last-child {
border-right: none;
}
[data-theme="dark"] .prose tbody tr {
border-bottom: 2px solid var(--color-dark-border);
}
[data-theme="dark"] .prose tbody tr:last-child {
border-bottom: none;
}
[data-theme="dark"] .prose tbody td {
color: var(--color-secondary-200);
border-right: 2px solid var(--color-dark-border);
border-bottom: 2px solid var(--color-dark-border);
}
[data-theme="dark"] .prose tbody td:last-child {
border-right: none;
}
[data-theme="dark"] .prose tbody tr:last-child td {
border-bottom: none;
}
[data-theme="dark"] .prose tbody tr:hover {
background-color: var(--color-dark-surface/60);
}
[data-theme="dark"] .prose tbody tr:nth-child(even) {
background-color: var(--color-dark-surface/40);
}
[data-theme="dark"] .prose tbody tr:nth-child(even):hover {
background-color: var(--color-dark-surface/60);
}
/* 暗色模式下的状态标记样式 */
[data-theme="dark"] .prose td .status-success {
background-color: rgba(16, 185, 129, 0.2);
color: rgb(110, 231, 183);
}
[data-theme="dark"] .prose td .status-warning {
background-color: rgba(245, 158, 11, 0.2);
color: rgb(253, 186, 116);
}
[data-theme="dark"] .prose td .status-error {
background-color: rgba(239, 68, 68, 0.2);
color: rgb(252, 165, 165);
/* 移除所有表格相关样式 */
}
/* 收纳内容样式 */

View File

@ -1,8 +1,8 @@
@import "tailwindcss";
@import "./prism.css";
@import "./content-styles.css";
@import "./table-styles.css";
@import "./emoji.css";
@import "./code-blocks.css";
@import "./header.css";
/* 定义深色模式选择器 */
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
@ -12,25 +12,6 @@
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease;
}
/* 专门为卡片元素添加过渡属性,修复悬停问题 */
.recent-article,
[class*="hover:-translate-y"] {
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease, transform 0.3s ease;
/* 添加transform-style属性以优化渲染 */
transform-style: preserve-3d;
/* 添加will-change提示浏览器将使用GPU加速 */
will-change: transform;
/* 确保初始状态是稳定的 */
transform: translateY(0);
/* 防止鼠标在卡片内移动时触发重新计算 */
backface-visibility: hidden;
}
/* 使用更具体的选择器优先级来控制悬停行为 */
.recent-article:hover,
[class*="hover:-translate-y"]:hover {
transform: translateY(-0.25rem) !important;
}
@theme {
/* 主色调 - 使用更现代的蓝紫色 */

View File

@ -4,14 +4,185 @@
#header-bg.scrolled {
backdrop-filter: blur(6px);
background: rgba(249, 250, 251, 0.8);
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.04),
0 2px 4px rgba(0, 0, 0, 0.04),
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 2px 4px rgba(0, 0, 0, 0.04),
0 4px 8px rgba(0, 0, 0, 0.04);
}
/* 黑暗模式样式 */
[data-theme="dark"] #header-bg.scrolled {
background: rgba(15, 23, 42, 0.8);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
}
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3),
0 2px 4px -1px rgba(0, 0, 0, 0.2);
}
/* 通用搜索框样式 */
.pagefind-ui .pagefind-ui__form {
margin: 0;
max-width: none;
position: relative; /* 确保定位上下文 */
}
.pagefind-ui .pagefind-ui__search-input {
border-radius: 9999px !important;
font-size: 0.875rem !important;
height: 32px !important;
width: 12rem !important;
padding-top: 0.25rem !important;
padding-bottom: 0.25rem !important;
padding-left: 35px !important;
padding-right: 47px !important;
border-color: var(--color-gray-200) !important;
border-width: 1px !important;
color: var(--color-gray-700) !important;
font-weight: 400 !important;
background-color: var(--color-gray-50) !important;
}
/* 移动端搜索框样式 */
#mobile-search-panel .pagefind-ui .pagefind-ui__search-input {
width: 100% !important;
height:35px!important;
}
#mobile-search-panel .pagefind-ui__search-input {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
[data-theme="dark"] .pagefind-ui .pagefind-ui__search-input {
color: var(--color-gray-200) !important;
border-color: var(--color-gray-700) !important;
background-color: var(--color-gray-800) !important;
}
.pagefind-ui .pagefind-ui__search-input::placeholder {
color: var(--color-gray-500) !important;
opacity: 1 !important;
}
[data-theme="dark"] .pagefind-ui .pagefind-ui__search-input::placeholder {
color: var(--color-gray-400) !important;
}
.pagefind-ui .pagefind-ui__search-input:hover {
border-color: var(--color-primary-300) !important;
}
[data-theme="dark"] .pagefind-ui .pagefind-ui__search-input:hover {
border-color: var(--color-primary-600) !important;
}
.pagefind-ui *:focus-visible {
outline:0 transparent !important;
}
.pagefind-ui .pagefind-ui__search-input:focus {
background-color: white !important;
opacity: 1 !important;
border-width: 2px !important;
border-color: var(--color-primary-300) !important;
}
[data-theme="dark"] .pagefind-ui .pagefind-ui__search-input:focus {
background-color: var(--color-gray-800) !important;
border-color: var(--color-primary-600) !important;
}
/* 修复搜索图标位置 */
.pagefind-ui .pagefind-ui__form::before {
content: "";
position: absolute;
top: 50% !important;
transform: translateY(-50%) !important;
left: 12px !important;
z-index: 10;
background-color: var(--color-gray-400) !important;
}
/* 搜索框清除按钮垂直居中 */
.pagefind-ui .pagefind-ui__search-clear {
transform: translateY(-20%);
background: none !important;
font-size: 0.8125rem !important;
color: var(--color-gray-400) !important;
}
/* 搜索结果抽屉 */
.pagefind-ui .pagefind-ui__drawer{
position: absolute;
left: 0;
right: 0;
margin-top: 0.25rem;
z-index: 50;
padding: 0.75rem;
overflow-y: auto;
max-height: 70vh;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
0 4px 6px -2px rgba(0, 0, 0, 0.05);
border-radius: 8px;
border: 1px solid var(--color-gray-200);
background-color: var(--color-gray-50);
width: 100%; /* 确保抽屉宽度不超过容器 */
max-width: 100%; /* 限制最大宽度 */
overflow-x: hidden; /* 防止横向滚动 */
color:var(--color-gray-500);
}
[data-theme="dark"] .pagefind-ui .pagefind-ui__drawer{
border: 1px solid var(--color-gray-700);
background-color: var(--color-gray-800);
color:var(--color-gray-400);
}
.pagefind-ui .pagefind-ui__results-area {
margin-top: 0 !important;
}
/* 搜索结果消息文本 */
.pagefind-ui .pagefind-ui__message {
font-size: 0.55rem;
word-wrap: break-word; /* 允许长消息文本换行 */
padding-top: 0 !important;
padding-bottom: 10px !important;
}
/* 搜索结果内容 */
.pagefind-ui .pagefind-ui__drawer ol,
.pagefind-ui .pagefind-ui__drawer div,
.pagefind-ui .pagefind-ui__drawer p,
.pagefind-ui .pagefind-ui__drawer mark {
word-wrap: break-word;
min-width: 100% !important;
max-width: 100% !important;
width: 100% !important;
}
.pagefind-ui .pagefind-ui__result {
padding: 2px 0px !important;
}
.pagefind-ui .pagefind-ui__result-link{
color:var(--color-gray-800) !important;
font-size: 1rem; /* 等于 14px */
line-height: 1.5rem; /* 等于 20px */
}
[data-theme="dark"] .pagefind-ui .pagefind-ui__result-link{
color:var(--color-gray-200) !important;
}
.pagefind-ui .pagefind-ui__result-title{
color:var(--color-gray-600) !important;
font-size: 0.875rem; /* 等于 14px */
line-height: 1.25rem; /* 等于 20px */
}
[data-theme="dark"] .pagefind-ui .pagefind-ui__result-title{
color:var(--color-gray-400) !important;
}

View File

@ -1,137 +0,0 @@
/* PrismJS 1.29.0
https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript+bash+jsx+tsx+typescript */
/**
* prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML
* Based on https://github.com/chriskempson/tomorrow-theme
* @author Rose Pritchard
*/
code[class*="language-"],
pre[class*="language-"] {
color: #abb2bf !important;
background: none;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
border: 1px solid #374151;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
border-radius: 0.5rem;
background: #282c34 !important;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #282c34 !important;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.block-comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #7f848e;
}
.token.punctuation {
color: #abb2bf;
}
.token.tag,
.token.attr-name,
.token.namespace,
.token.deleted {
color: #e06c75;
}
.token.function-name {
color: #61afef;
}
.token.boolean,
.token.number,
.token.function {
color: #d19a66;
}
.token.property,
.token.class-name,
.token.constant,
.token.symbol {
color: #e5c07b;
}
.token.selector,
.token.important,
.token.atrule,
.token.keyword,
.token.builtin {
color: #c678dd;
}
.token.string,
.token.char,
.token.attr-value,
.token.regex,
.token.variable {
color: #98c379;
}
.token.operator,
.token.entity,
.token.url {
color: #56b6c2;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
.token.inserted {
color: green;
}
[data-theme="dark"] code[class*="language-"],
[data-theme="dark"] pre[class*="language-"] {
color: #abb2bf !important;
background: #282c34 !important;
}
[data-theme="dark"] :not(pre) > code[class*="language-"],
[data-theme="dark"] pre[class*="language-"] {
background: #282c34 !important;
}

665
src/styles/table-styles.css Normal file
View File

@ -0,0 +1,665 @@
/* 表格基础样式 */
table {
width: 100%;
border-collapse: collapse;
margin: 1.5em 0;
font-size: 0.95rem;
box-shadow: 0 4px 10px -1px rgba(0, 0, 0, 0.08), 0 2px 4px -1px rgba(0, 0, 0, 0.04);
overflow: hidden;
border-radius: 0.5rem;
}
/* 表格边框样式 */
table, th, td {
border: 1px solid #e2e8f0;
}
/* 表头样式 */
thead {
background-color: #f0f5ff;
}
th {
padding: 1rem;
font-weight: 600;
text-align: left;
vertical-align: middle;
border-bottom: 2px solid #d6e0ff;
color: #3451db;
}
/* 表格单元格样式 */
td {
padding: 0.875rem 1rem;
vertical-align: middle;
color: #1e293b;
position: relative;
}
/* 内容对齐方式 */
th[align="center"],
td[align="center"] {
text-align: center;
}
th[align="right"],
td[align="right"] {
text-align: right;
}
th[align="left"],
td[align="left"] {
text-align: left;
}
/* 表格行交替颜色 */
tbody tr:nth-child(even) {
background-color: #f8fafc;
}
tbody tr:nth-child(odd) {
background-color: #ffffff;
}
/* 鼠标悬停效果 */
tbody tr:hover {
background-color: #f5f7ff;
}
/* 响应式表格 */
@media (max-width: 640px) {
table {
display: block;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
th, td {
white-space: nowrap;
padding: 0.75rem 1rem;
}
}
/* 特定针对于内联样式的表格 */
table[style] th,
table[style] td {
border: 1px solid #e2e8f0 !important;
}
/* 处理嵌套样式的情况 */
.prose table {
border-collapse: collapse !important;
border-radius: 0.5rem !important;
overflow: hidden !important;
}
.prose table th,
.prose table td {
border: 1px solid #e2e8f0 !important;
}
/* 勾号和叉号样式 - 通用方式 */
.tick, .yes, .true {
display: inline-block;
text-align: center;
color: #059669;
font-weight: bold;
font-size: 1.25em;
}
.cross, .no, .false {
display: inline-block;
text-align: center;
color: #dc2626;
font-weight: bold;
font-size: 1.25em;
}
/* 添加内容 */
.tick::before, .yes::before, .true::before {
content: "✓";
}
.cross::before, .no::before, .false::before {
content: "✗";
}
/* 直接使用✓或✗的样式 */
.check-mark {
color: #059669;
font-weight: bold;
font-size: 1.25em;
text-align: center;
}
.cross-mark {
color: #dc2626;
font-weight: bold;
font-size: 1.25em;
text-align: center;
}
/* 状态标识符号样式 */
td:has(> :is(svg[aria-label="yes"], .yes, .tick, .true)) {
color: #047857;
}
td:has(> :is(svg[aria-label="no"], .no, .cross, .false)) {
color: #b91c1c;
}
/* 对表格中直接使用的✓和✗符号进行样式处理 */
td:only-child {
text-align: center;
}
/* 检查整个表格的样式 */
table.feature-comparison td {
text-align: center;
}
table.feature-comparison td:first-child {
text-align: left;
}
/* 添加全局样式识别 */
.text-success,
.text-green,
.success-mark {
color: #059669 !important;
font-weight: bold;
}
.text-danger,
.text-red,
.danger-mark {
color: #dc2626 !important;
font-weight: bold;
}
/* 直接针对表格中的✓和✗符号 - 移除不兼容的:contains选择器 */
/* 改用更通用的选择器和类 */
/* 针对行内样式的表格 */
table[style] td {
vertical-align: middle;
}
/* 使用属性选择器 */
td[align="center"] {
text-align: center;
}
/* 为常见的功能对比表格添加专用样式 */
.feature-table th,
.function-table th,
.comparison-table th {
text-align: center;
}
.feature-table th:first-child,
.function-table th:first-child,
.comparison-table th:first-child {
text-align: left;
}
.feature-table td,
.function-table td,
.comparison-table td {
text-align: center;
}
.feature-table td:first-child,
.function-table td:first-child,
.comparison-table td:first-child {
text-align: left;
font-weight: 500;
}
/* 直接样式化 ✓ 和 ✗ 字符 */
.checkmark, .check, . {
color: #059669;
font-weight: bold;
font-size: 1.25em;
}
.crossmark, .cross-symbol, . {
color: #dc2626;
font-weight: bold;
font-size: 1.25em;
}
/* 暗色模式 - 只使用 [data-theme="dark"] 选择器 */
[data-theme="dark"] {
table {
box-shadow: 0 4px 12px -1px rgba(0, 0, 0, 0.3), 0 2px 6px -1px rgba(0, 0, 0, 0.2);
border-color: #475569;
background-color: #0f172a;
}
table, th, td {
border-color: #475569;
}
table[style] th,
table[style] td,
.prose table th,
.prose table td {
border-color: #475569 !important;
}
thead {
background-color: #1a2e6a;
border-bottom: 2px solid #4b6bff;
}
th {
border-bottom-color: #4b6bff;
color: #adc2ff;
background-color: transparent;
font-weight: 600;
letter-spacing: 0.5px;
}
th:not(:last-child) {
border-right-color: #374151;
}
td {
color: #e2e8f0;
}
td:not(:last-child) {
border-right-color: #374151;
}
tbody tr:nth-child(even) {
background-color: #1e293b;
}
tbody tr:nth-child(odd) {
background-color: #111827;
}
tbody tr:hover {
background-color: #272f45;
box-shadow: inset 0 0 0 2px rgba(75, 107, 255, 0.3);
}
tbody tr:last-child td {
border-bottom: none;
}
/* 表格圆角样式在暗色模式下的处理 */
table {
border-radius: 0.5rem;
overflow: hidden;
}
/* 勾号和叉号颜色调整 */
.tick, .yes, .true, .check-mark {
color: #10b981;
text-shadow: 0 0 5px rgba(16, 185, 129, 0.3);
}
.cross, .no, .false, .cross-mark {
color: #ef4444;
text-shadow: 0 0 5px rgba(239, 68, 68, 0.3);
}
.text-success,
.text-green,
.success-mark {
color: #10b981 !important;
text-shadow: 0 0 5px rgba(16, 185, 129, 0.3);
}
.text-danger,
.text-red,
.danger-mark {
color: #ef4444 !important;
text-shadow: 0 0 5px rgba(239, 68, 68, 0.3);
}
.checkmark, .check, . {
color: #10b981;
text-shadow: 0 0 5px rgba(16, 185, 129, 0.3);
}
.crossmark, .cross-symbol, . {
color: #ef4444;
text-shadow: 0 0 5px rgba(239, 68, 68, 0.3);
}
}/* 表格基础样式 */
table {
width: 100%;
border-collapse: collapse;
margin: 1.5em 0;
font-size: 0.95rem;
box-shadow: 0 4px 10px -1px rgba(0, 0, 0, 0.08), 0 2px 4px -1px rgba(0, 0, 0, 0.04);
overflow: hidden;
border-radius: 0.5rem;
}
/* 表格边框样式 */
table, th, td {
border: 1px solid #e2e8f0;
}
/* 表头样式 */
thead {
background-color: #f0f5ff;
}
th {
padding: 1rem;
font-weight: 600;
text-align: left;
vertical-align: middle;
border-bottom: 2px solid #d6e0ff;
color: #3451db;
}
/* 表格单元格样式 */
td {
padding: 0.875rem 1rem;
vertical-align: middle;
color: #1e293b;
position: relative;
}
/* 内容对齐方式 */
th[align="center"],
td[align="center"] {
text-align: center;
}
th[align="right"],
td[align="right"] {
text-align: right;
}
th[align="left"],
td[align="left"] {
text-align: left;
}
/* 表格行交替颜色 */
tbody tr:nth-child(even) {
background-color: #f8fafc;
}
tbody tr:nth-child(odd) {
background-color: #ffffff;
}
/* 鼠标悬停效果 */
tbody tr:hover {
background-color: #f5f7ff;
}
/* 响应式表格 */
@media (max-width: 640px) {
table {
display: block;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
th, td {
white-space: nowrap;
padding: 0.75rem 1rem;
}
}
/* 特定针对于内联样式的表格 */
table[style] th,
table[style] td {
border: 1px solid #e2e8f0 !important;
}
/* 处理嵌套样式的情况 */
.prose table {
border-collapse: collapse !important;
border-radius: 0.5rem !important;
overflow: hidden !important;
}
.prose table th,
.prose table td {
border: 1px solid #e2e8f0 !important;
}
/* 勾号和叉号样式 - 通用方式 */
.tick, .yes, .true {
display: inline-block;
text-align: center;
color: #059669;
font-weight: bold;
font-size: 1.25em;
}
.cross, .no, .false {
display: inline-block;
text-align: center;
color: #dc2626;
font-weight: bold;
font-size: 1.25em;
}
/* 添加内容 */
.tick::before, .yes::before, .true::before {
content: "✓";
}
.cross::before, .no::before, .false::before {
content: "✗";
}
/* 直接使用✓或✗的样式 */
.check-mark {
color: #059669;
font-weight: bold;
font-size: 1.25em;
text-align: center;
}
.cross-mark {
color: #dc2626;
font-weight: bold;
font-size: 1.25em;
text-align: center;
}
/* 状态标识符号样式 */
td:has(> :is(svg[aria-label="yes"], .yes, .tick, .true)) {
color: #047857;
}
td:has(> :is(svg[aria-label="no"], .no, .cross, .false)) {
color: #b91c1c;
}
/* 对表格中直接使用的✓和✗符号进行样式处理 */
td:only-child {
text-align: center;
}
/* 检查整个表格的样式 */
table.feature-comparison td {
text-align: center;
}
table.feature-comparison td:first-child {
text-align: left;
}
/* 添加全局样式识别 */
.text-success,
.text-green,
.success-mark {
color: #059669 !important;
font-weight: bold;
}
.text-danger,
.text-red,
.danger-mark {
color: #dc2626 !important;
font-weight: bold;
}
/* 直接针对表格中的✓和✗符号 - 移除不兼容的:contains选择器 */
/* 改用更通用的选择器和类 */
/* 针对行内样式的表格 */
table[style] td {
vertical-align: middle;
}
/* 使用属性选择器 */
td[align="center"] {
text-align: center;
}
/* 为常见的功能对比表格添加专用样式 */
.feature-table th,
.function-table th,
.comparison-table th {
text-align: center;
}
.feature-table th:first-child,
.function-table th:first-child,
.comparison-table th:first-child {
text-align: left;
}
.feature-table td,
.function-table td,
.comparison-table td {
text-align: center;
}
.feature-table td:first-child,
.function-table td:first-child,
.comparison-table td:first-child {
text-align: left;
font-weight: 500;
}
/* 直接样式化 ✓ 和 ✗ 字符 */
.checkmark, .check, . {
color: #059669;
font-weight: bold;
font-size: 1.25em;
}
.crossmark, .cross-symbol, . {
color: #dc2626;
font-weight: bold;
font-size: 1.25em;
}
/* 暗色模式 - 只使用 [data-theme="dark"] 选择器 */
[data-theme="dark"] {
table {
box-shadow: 0 4px 12px -1px rgba(0, 0, 0, 0.3), 0 2px 6px -1px rgba(0, 0, 0, 0.2);
border-color: #475569;
background-color: #0f172a;
}
table, th, td {
border-color: #475569;
}
table[style] th,
table[style] td,
.prose table th,
.prose table td {
border-color: #475569 !important;
}
thead {
background-color: #1e293b;
border-bottom: 2px solid #4b6bff;
}
th {
border-bottom-color: #4b6bff;
color: #adc2ff;
background-color: transparent;
font-weight: 600;
letter-spacing: 0.5px;
}
th:not(:last-child) {
border-right-color: #374151;
}
td {
color: #e2e8f0;
}
td:not(:last-child) {
border-right-color: #374151;
}
tbody tr:nth-child(even) {
background-color: #1e293b;
}
tbody tr:nth-child(odd) {
background-color: #111827;
}
tbody tr:hover {
background-color: #272f45;
box-shadow: inset 0 0 0 2px rgba(75, 107, 255, 0.3);
}
tbody tr:last-child td {
border-bottom: none;
}
/* 表格圆角样式在暗色模式下的处理 */
table {
border-radius: 0.5rem;
overflow: hidden;
}
/* 勾号和叉号颜色调整 */
.tick, .yes, .true, .check-mark {
color: #10b981;
text-shadow: 0 0 5px rgba(16, 185, 129, 0.3);
}
.cross, .no, .false, .cross-mark {
color: #ef4444;
text-shadow: 0 0 5px rgba(239, 68, 68, 0.3);
}
.text-success,
.text-green,
.success-mark {
color: #10b981 !important;
text-shadow: 0 0 5px rgba(16, 185, 129, 0.3);
}
.text-danger,
.text-red,
.danger-mark {
color: #ef4444 !important;
text-shadow: 0 0 5px rgba(239, 68, 68, 0.3);
}
.checkmark, .check, . {
color: #10b981;
text-shadow: 0 0 5px rgba(16, 185, 129, 0.3);
}
.crossmark, .cross-symbol, . {
color: #ef4444;
text-shadow: 0 0 5px rgba(239, 68, 68, 0.3);
}
}