优化导航栏样式

This commit is contained in:
lsy 2025-05-08 11:21:31 +08:00
parent 6cded9ab51
commit 55a928b545
5 changed files with 374 additions and 126 deletions

View File

@ -125,9 +125,6 @@ export default defineConfig({
// 启用代码换行 // 启用代码换行
wrap: true wrap: true
}, },
remarkPlugins: [
[remarkEmoji, { emoticon: false, padded: true }]
],
rehypePlugins: [ rehypePlugins: [
[rehypeExternalLinks, { target: '_blank', rel: ['nofollow', 'noopener', 'noreferrer'] }], [rehypeExternalLinks, { target: '_blank', rel: ['nofollow', 'noopener', 'noreferrer'] }],
rehypeCodeBlocks, rehypeCodeBlocks,

View File

@ -237,8 +237,6 @@ import { VISITED_PLACES } from '@/consts';
### Mermaid 图表支持 ### Mermaid 图表支持
你可以在 Markdown 文件中使用 Mermaid 语法创建各种图表:
````markdown ````markdown
```mermaid ```mermaid
graph TD; graph TD;
@ -249,20 +247,13 @@ graph TD;
``` ```
```` ````
系统将自动渲染这些图表,并支持深色/浅色主题自动适应。 ```mermaid
graph TD;
### 代码块样式优化 A[开始] -->|处理数据| B(处理结果);
B --> C{判断条件};
代码块支持多种主题和语言高亮,内置了复制按钮: C -->|条件1| D[结果1];
C -->|条件2| E[结果2];
````markdown
```javascript
// 示例代码
function example() {
console.log("Hello, world!");
}
``` ```
````
## SEO 优化 ## SEO 优化

View File

@ -1,5 +1,5 @@
--- ---
title: "MDX使用教程" title: "markdown使用教程"
date: 2023-03-03 date: 2023-03-03
tags: [] tags: []
--- ---
@ -51,14 +51,6 @@ tags: []
~~这是删除线文本~~ ~~这是删除线文本~~
#### 1.2.5 下划线文本
```markdown
<u>这是下划线文本</u>
```
<u>这是下划线文本</u>
### 1.3 列表 ### 1.3 列表
#### 1.3.1 无序列表 #### 1.3.1 无序列表
@ -204,58 +196,3 @@ function greet(user: User): string {
<br/> <br/>
--- ---
### 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 标签
#### 2.1.1 下划线文本
```mdx
<u>这是下划线文本</u>
```
<u>这是下划线文本</u>
#### 2.1.2 收纳语法
```mdx
<details>
<summary>点击展开</summary>
这里是被收纳的内容。
可以包含任何 MDX 格式的内容。
- 列表项1
- 列表项2
- 列表项3
</details>
```
<details>
<summary>点击展开</summary>
这里是被收纳的内容。
可以包含任何 MDX 格式的内容。
- 列表项1
- 列表项2
- 列表项3
</details>

View File

@ -180,31 +180,90 @@ function generateTableOfContents(headings: Heading[]) {
// 查找最低级别的标题(数值最小) // 查找最低级别的标题(数值最小)
const minDepth = Math.min(...headings.map((h) => h.depth)); const minDepth = Math.min(...headings.map((h) => h.depth));
let tocHtml = '<ul class="space-y-2">'; // 按照标题层级构建嵌套结构
const tocTree: any[] = [];
const levelMap: Record<number, any[]> = {};
headings.forEach((heading) => { headings.forEach((heading) => {
// 计算相对缩进,以最小深度为基准
const relativeDepth = heading.depth - minDepth; const relativeDepth = heading.depth - minDepth;
const indent = relativeDepth * 0.75;
// 基于相对深度而非绝对深度决定样式 // 构建标题项
const isHigherLevel = relativeDepth <= 1; // 仅最高级和次高级标题使用较重的样式 const headingItem = {
slug: heading.slug,
text: heading.text,
depth: relativeDepth,
children: [],
};
tocHtml += `<li> // 更精确地处理嵌套关系
<a href="#${heading.slug}" if (relativeDepth === 0) {
class="block hover:text-primary-600 dark:hover:text-primary-400 duration-50 ${ // 顶级标题直接加入到树中
tocTree.push(headingItem);
levelMap[0] = tocTree;
} else {
// 查找当前标题的父级
let parentDepth = relativeDepth - 1;
// 向上查找可能的父级
while (parentDepth >= 0 && !levelMap[parentDepth]) {
parentDepth--;
}
if (parentDepth >= 0 && levelMap[parentDepth] && levelMap[parentDepth].length > 0) {
// 找到父层级,将此标题添加到最近的父标题的子标题数组中
const parentItems = levelMap[parentDepth];
const parent = parentItems[parentItems.length - 1];
parent.children.push(headingItem);
// 更新当前深度的映射
if (!levelMap[relativeDepth]) {
levelMap[relativeDepth] = [];
}
levelMap[relativeDepth].push(headingItem);
} else {
// 找不到有效父级,作为顶级标题处理
tocTree.push(headingItem);
levelMap[relativeDepth] = [headingItem];
}
}
});
// 递归生成HTML
function generateTocHTML(items: any[], level = 0) {
if (items.length === 0) return '';
const isTopLevel = level === 0;
let html = `<ul class="space-y-2 toc-list ${isTopLevel ? '' : 'toc-sublist hidden'}" ${level > 0 ? 'aria-expanded="false"' : ''}>`;
items.forEach(item => {
const hasChildren = item.children && item.children.length > 0;
const isHigherLevel = item.depth <= 1; // 只有最高级和次高级标题使用较重的样式
html += `<li class="toc-item" data-depth="${item.depth}">
<div class="toc-item-container">
<a href="#${item.slug}"
class="toc-link block duration-50 ${
isHigherLevel isHigherLevel
? "text-secondary-800 dark:text-secondary-200 font-medium" ? "text-secondary-800 dark:text-secondary-200 font-medium"
: "text-secondary-600 dark:text-secondary-400" : "text-secondary-600 dark:text-secondary-400"
}" }"
style="padding-left: ${indent}rem;"> style="padding-left: ${item.depth * 0.75}rem;">
${heading.text} ${item.text}
</a> </a>
${hasChildren ? `<button class="toc-toggle ml-1 p-1 text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400" aria-expanded="false">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>` : ''}
</div>
${generateTocHTML(item.children, level + 1)}
</li>`; </li>`;
}); });
tocHtml += "</ul>"; html += '</ul>';
return tocHtml; return html;
}
return generateTocHTML(tocTree);
} }
// 生成目录HTML // 生成目录HTML
@ -378,18 +437,14 @@ const tableOfContents = generateTableOfContents(headings);
<!-- 目录 --> <!-- 目录 -->
<section <section
class="hidden 2xl:block fixed right-[calc(50%-44rem)] top-20 w-64 z-30" class="hidden 2xl:block"
id="toc-panel" id="toc-panel"
> >
<div>
<div <div
class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 flex flex-col backdrop-blur-sm bg-opacity-95 dark:bg-opacity-95" class="panel-header"
>
<div
class="border-b border-secondary-100 dark:border-gray-700 p-4 pb-3 sticky top-0 bg-white dark:bg-gray-800 bg-opacity-95 dark:bg-opacity-95 backdrop-filter backdrop-blur-sm z-10 rounded-t-xl"
>
<h3
class="font-bold text-primary-700 dark:text-primary-400 flex items-center gap-2"
> >
<h3>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4" class="h-4 w-4"
@ -409,7 +464,7 @@ const tableOfContents = generateTableOfContents(headings);
</div> </div>
<div <div
id="toc-content" id="toc-content"
class="text-sm p-4 pt-2 overflow-y-auto max-h-[calc(100vh-8rem-42px)] scrollbar-thin scrollbar-thumb-primary-200 dark:scrollbar-thumb-primary-800 scrollbar-track-transparent" class="scrollbar-thin scrollbar-thumb-primary-200 dark:scrollbar-thumb-primary-800 scrollbar-track-transparent"
set:html={tableOfContents} set:html={tableOfContents}
> >
<!-- 目录内容在服务端生成 --> <!-- 目录内容在服务端生成 -->
@ -742,8 +797,42 @@ const tableOfContents = generateTableOfContents(headings);
addListener(window, "resize", checkTocVisibility); addListener(window, "resize", checkTocVisibility);
checkTocVisibility(); checkTocVisibility();
// 处理目录折叠/展开功能
const tocToggles = tocContent.querySelectorAll(".toc-toggle");
tocToggles.forEach((toggle) => {
addListener(toggle, "click", (e) => {
e.preventDefault();
e.stopPropagation();
const expanded = toggle.getAttribute("aria-expanded") === "true";
toggle.setAttribute("aria-expanded", expanded ? "false" : "true");
// 更新图标旋转
const svg = toggle.querySelector("svg");
if (svg) {
svg.style.transform = expanded ? "" : "rotate(-180deg)";
}
// 切换子菜单显示状态
const listItem = toggle.closest(".toc-item");
if (listItem) {
const sublist = listItem.querySelector(".toc-sublist");
if (sublist) {
if (expanded) {
sublist.classList.add("hidden");
sublist.setAttribute("aria-expanded", "false");
} else {
sublist.classList.remove("hidden");
sublist.setAttribute("aria-expanded", "true");
}
}
}
});
});
// 处理目录链接点击跳转 // 处理目录链接点击跳转
const tocLinks = tocContent.querySelectorAll("a"); const tocLinks = tocContent.querySelectorAll(".toc-link");
tocLinks.forEach((link) => { tocLinks.forEach((link) => {
addListener(link, "click", (e) => { addListener(link, "click", (e) => {
@ -790,7 +879,8 @@ const tableOfContents = generateTableOfContents(headings);
const headings = Array.from( const headings = Array.from(
article.querySelectorAll("h1, h2, h3, h4, h5, h6"), article.querySelectorAll("h1, h2, h3, h4, h5, h6"),
); );
const tocLinks = Array.from(tocContent.querySelectorAll("a")); const tocLinks = Array.from(tocContent.querySelectorAll(".toc-link"));
const tocItems = Array.from(tocContent.querySelectorAll(".toc-item"));
// 清除所有活动状态 // 清除所有活动状态
tocLinks.forEach((link) => { tocLinks.forEach((link) => {
@ -815,7 +905,10 @@ const tableOfContents = generateTableOfContents(headings);
} }
} }
// 高亮当前标题对应的目录项 // 记录当前活动的项目和其所有父级
const activeItems = new Set();
// 高亮当前标题对应的目录项并展开父菜单
if (currentHeading) { if (currentHeading) {
const id = currentHeading.getAttribute("id"); const id = currentHeading.getAttribute("id");
if (id) { if (id) {
@ -830,8 +923,34 @@ const tableOfContents = generateTableOfContents(headings);
"font-medium", "font-medium",
); );
// 可选: 确保当前激活的目录项在可视区域内 // 展开当前激活项的所有父菜单并收集到活动项集合中
const tocContainer = tocContent.querySelector("ul"); let parent = activeLink.closest(".toc-item");
while (parent) {
// 添加到活动项目集合
activeItems.add(parent);
const parentSublist = parent.querySelector(".toc-sublist");
const parentToggle = parent.querySelector(".toc-toggle");
if (parentSublist && parentSublist.classList.contains("hidden")) {
parentSublist.classList.remove("hidden");
parentSublist.setAttribute("aria-expanded", "true");
if (parentToggle) {
parentToggle.setAttribute("aria-expanded", "true");
const svg = parentToggle.querySelector("svg");
if (svg) {
svg.style.transform = "rotate(-180deg)";
}
}
}
// 向上查找父级
parent = parent.parentElement?.closest(".toc-item");
}
// 确保当前激活的目录项在可视区域内
const tocContainer = tocContent;
if (tocContainer) { if (tocContainer) {
const linkOffsetTop = activeLink.offsetTop; const linkOffsetTop = activeLink.offsetTop;
const containerScrollTop = tocContainer.scrollTop; const containerScrollTop = tocContainer.scrollTop;
@ -849,6 +968,28 @@ const tableOfContents = generateTableOfContents(headings);
} }
} }
} }
// 关闭不在当前活动路径上的所有子菜单
tocItems.forEach(item => {
// 如果不在活动路径上且有子菜单
if (!activeItems.has(item)) {
const sublist = item.querySelector('.toc-sublist');
const toggle = item.querySelector('.toc-toggle');
if (sublist && !sublist.classList.contains('hidden')) {
sublist.classList.add('hidden');
sublist.setAttribute('aria-expanded', 'false');
if (toggle) {
toggle.setAttribute('aria-expanded', 'false');
const svg = toggle.querySelector('svg');
if (svg) {
svg.style.transform = '';
}
}
}
}
});
} }
addListener(window, "scroll", () => { addListener(window, "scroll", () => {
@ -861,6 +1002,18 @@ const tableOfContents = generateTableOfContents(headings);
} }
}); });
// 初始化时收起所有子菜单
const topLevelToggles = tocContent.querySelectorAll(".toc-list > .toc-item > .toc-item-container > .toc-toggle");
topLevelToggles.forEach(toggle => {
toggle.setAttribute("aria-expanded", "false");
const sublist = toggle.closest(".toc-item").querySelector(".toc-sublist");
if (sublist) {
sublist.classList.add("hidden");
sublist.setAttribute("aria-expanded", "false");
}
});
// 初始更新一次活动标题,确保相关父菜单展开
updateActiveHeading(); updateActiveHeading();
} }

View File

@ -220,10 +220,7 @@
border-bottom-color: var(--color-primary-400); border-bottom-color: var(--color-primary-400);
} }
/* 响应式样式 */
@media (max-width: 640px) {
/* 移除所有表格相关样式 */
}
/* 收纳内容样式 */ /* 收纳内容样式 */
.details-content { .details-content {
@ -363,3 +360,176 @@
background: linear-gradient(to right, var(--color-primary-900/10), var(--color-dark-surface)); background: linear-gradient(to right, var(--color-primary-900/10), var(--color-dark-surface));
border-left-color: var(--color-primary-400); border-left-color: var(--color-primary-400);
} }
/* 目录菜单样式 */
/* 目录项样式 */
.toc-item-container {
display: flex;
align-items: center;
}
.toc-link {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 0.25rem 0;
border-radius: 0.25rem;
}
/* 黑暗模式文本颜色 */
.dark .text-secondary-800,
[data-theme="dark"] .text-secondary-800 {
color: var(--color-secondary-200);
}
.dark .text-secondary-600,
[data-theme="dark"] .text-secondary-600 {
color: var(--color-secondary-400);
}
.toc-link:hover {
background-color: rgba(0, 0, 0, 0.05);
color: var(--color-primary-600);
}
.dark .toc-link:hover,
[data-theme="dark"] .toc-link:hover {
background-color: rgba(255, 255, 255, 0.05);
color: var(--color-primary-400);
}
.toc-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 4px;
min-width: 1.5rem;
min-height: 1.5rem;
}
.toc-toggle:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.dark .toc-toggle:hover,
[data-theme="dark"] .toc-toggle:hover {
background-color: rgba(255, 255, 255, 0.1);
}
/* 动画效果 */
.toc-sublist.hidden {
max-height: 0;
opacity: 0;
overflow: hidden;
}
.toc-sublist:not(.hidden) {
max-height: 1000px;
opacity: 1;
}
/* 目录面板样式 */
#toc-panel {
position: fixed;
right: calc(50% - 44rem);
top: 5rem;
width: 16rem;
z-index: 30;
}
#toc-panel > div {
background-color: white;
border: 1px solid var(--color-gray-200);
border-radius: 0.75rem;
display: flex;
flex-direction: column;
backdrop-filter: blur(4px);
background-opacity: 0.95;
}
.dark #toc-panel > div,
[data-theme="dark"] #toc-panel > div {
background-color: var(--color-gray-800);
border-color: var(--color-gray-700);
background-opacity: 0.95;
}
#toc-panel .panel-header {
border-bottom: 1px solid var(--color-secondary-100);
padding: 1rem;
padding-bottom: 0.75rem;
position: sticky;
top: 0;
z-index: 10;
border-top-left-radius: 0.75rem;
border-top-right-radius: 0.75rem;
background-color: white;
background-opacity: 0.95;
backdrop-filter: blur(4px);
}
.dark #toc-panel .panel-header,
[data-theme="dark"] #toc-panel .panel-header {
border-bottom-color: var(--color-gray-700);
background-color: var(--color-gray-800);
}
#toc-panel h3 {
font-weight: 700;
color: var(--color-primary-700);
display: flex;
align-items: center;
gap: 0.5rem;
}
.dark #toc-panel h3,
[data-theme="dark"] #toc-panel h3 {
color: var(--color-primary-400);
}
#toc-content {
font-size: 0.875rem;
padding: 1rem;
padding-top: 0.5rem;
overflow-y: auto;
max-height: calc(100vh - 8rem - 42px);
background-color: white;
}
.dark #toc-content,
[data-theme="dark"] #toc-content {
background-color: var(--color-gray-800);
}
/* 目录列表样式 */
.toc-list {
list-style-type: none;
}
/* 黑暗模式适配 */
.dark .text-secondary-800,
[data-theme="dark"] .text-secondary-800 {
color: var(--color-secondary-200);
}
.dark .text-secondary-600,
[data-theme="dark"] .text-secondary-600 {
color: var(--color-secondary-400);
}
.dark .text-primary-600,
[data-theme="dark"] .text-primary-600 {
color: var(--color-primary-400);
}
/* 扩展目录高亮样式 */
.toc-link.text-primary-600 {
font-weight: 500;
}
.dark .toc-link.text-primary-600,
[data-theme="dark"] .toc-link.text-primary-600 {
font-weight: 500;
}