fix: improve doc link navigation and tree display
- Fix link resolution with proper relative/absolute path handling - Improve link styling with underline decoration - Hide leaf nodes from tree, only show directories - Fix log file path for packaged app
This commit is contained in:
82
.trae/documents/fix_doc_link_matching_plan.md
Normal file
82
.trae/documents/fix_doc_link_matching_plan.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# 修复文档链接匹配逻辑计划
|
||||
|
||||
## 1. 问题分析
|
||||
用户反馈点击 `rhi/buffer/d3d12/buffer.md` 中的 `[Initialize](initialize.md)` 链接时,错误跳转到了 `Logger::Initialize`(可能位于其他目录)。
|
||||
经过检查 `src/components/ApiDocViewer.tsx` 中的 `handleReferenceClick` 方法:
|
||||
|
||||
```typescript
|
||||
const handleReferenceClick = useCallback((href: string) => {
|
||||
const ref = href.replace(/\.md$/, '')
|
||||
const match = externalDocs.find(d => {
|
||||
const docPath = d.relativePath.replace(/\.md$/, '')
|
||||
return docPath === ref || docPath.endsWith('/' + ref.split('/').pop())
|
||||
})
|
||||
if (match) {
|
||||
setSelectedPath(match.relativePath)
|
||||
setCurrentContent(match.content)
|
||||
}
|
||||
}, [externalDocs])
|
||||
```
|
||||
|
||||
当前的匹配逻辑非常危险:`docPath.endsWith('/' + ref.split('/').pop())` 这一行意味着只要文件名匹配(例如 `initialize.md`),不管它在哪个目录下,都可能被匹配到。这导致了跨目录的同名文件匹配错误。
|
||||
|
||||
## 2. 修复方案
|
||||
|
||||
### 修改文件: `src/components/ApiDocViewer.tsx`
|
||||
|
||||
我们需要实现基于当前文档路径的相对路径解析逻辑。由于 `handleReferenceClick` 目前只接收 `href`,我们需要让它知道当前所在的文档路径(`selectedPath`),以便正确解析相对路径。
|
||||
|
||||
1. **依赖当前路径**:确保 `handleReferenceClick` 能访问到 `selectedPath`。
|
||||
2. **实现相对路径解析**:
|
||||
- 如果链接以 `/` 开头,视为基于文档根目录的绝对路径。
|
||||
- 如果是相对路径(`./`、`../` 或直接文件名),则基于当前文档的目录进行解析。
|
||||
- 解析出目标文件的完整 `relativePath` 后,再在 `externalDocs` 中查找精确匹配。
|
||||
|
||||
**修改后的逻辑伪代码**:
|
||||
```typescript
|
||||
const handleReferenceClick = useCallback((href: string) => {
|
||||
// 1. 获取当前文档所在的目录
|
||||
const currentDir = selectedPath ? selectedPath.split('/').slice(0, -1).join('/') : '';
|
||||
|
||||
// 2. 解析目标路径
|
||||
let targetPath = '';
|
||||
if (href.startsWith('/')) {
|
||||
// 绝对路径(相对于文档根目录)
|
||||
targetPath = href.slice(1);
|
||||
} else {
|
||||
// 相对路径处理
|
||||
const parts = currentDir ? currentDir.split('/') : [];
|
||||
const hrefParts = href.split('/');
|
||||
|
||||
for (const part of hrefParts) {
|
||||
if (part === '.') continue;
|
||||
if (part === '..') {
|
||||
parts.pop();
|
||||
} else {
|
||||
parts.push(part);
|
||||
}
|
||||
}
|
||||
targetPath = parts.join('/');
|
||||
}
|
||||
|
||||
// 3. 规范化目标路径(移除 .md 后缀以便对比,或者直接对比全名)
|
||||
// 考虑到 externalDocs 中的 relativePath 包含 .md,我们也应该构造包含 .md 的路径
|
||||
if (!targetPath.endsWith('.md')) {
|
||||
targetPath += '.md';
|
||||
}
|
||||
|
||||
// 4. 精确查找
|
||||
const match = externalDocs.find(d => d.relativePath === targetPath);
|
||||
|
||||
if (match) {
|
||||
setSelectedPath(match.relativePath)
|
||||
setCurrentContent(match.content)
|
||||
} else {
|
||||
console.warn(`Document not found: ${targetPath}`);
|
||||
}
|
||||
}, [externalDocs, selectedPath]) // 添加 selectedPath 依赖
|
||||
```
|
||||
|
||||
## 3. 影响与验证
|
||||
- **影响**:修复了所有相对路径链接的跳转逻辑,不再会出现跨目录同名文件乱跳的情况。
|
||||
- **验证**:点击 `rhi/buffer/d3d12/buffer.md` 中的 `initialize.md`,应该正确跳转到同目录下的 `rhi/buffer/d3d12/initialize.md`。
|
||||
68
.trae/documents/fix_link_color_plan.md
Normal file
68
.trae/documents/fix_link_color_plan.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# 修复文档内代码块中链接样式失效问题
|
||||
|
||||
## 1. 问题分析
|
||||
用户反馈“有的是蓝色,有的没有颜色(普通文本)”。
|
||||
在 Markdown 文档(特别是 API 文档)中,链接经常出现在代码块或表格内。例如:
|
||||
`[`Initialize`](initialize.md)`
|
||||
|
||||
在 `DocContent.tsx` 中,我们对 `code` 标签有自定义渲染逻辑:
|
||||
```typescript
|
||||
code: ({ className, children, ...props }) => {
|
||||
const match = /language-(\w+)/.exec(className || '')
|
||||
if (match) {
|
||||
// ... 代码高亮块 ...
|
||||
}
|
||||
// 行内代码
|
||||
return (
|
||||
<code className="text-zinc-300 font-mono text-lg" {...props}>
|
||||
{children}
|
||||
</code>
|
||||
)
|
||||
},
|
||||
```
|
||||
|
||||
当 Markdown 解析器遇到 `[`Initialize`](initialize.md)` 时,它会生成:
|
||||
```html
|
||||
<a href="initialize.md"><code>Initialize</code></a>
|
||||
```
|
||||
或者(取决于解析器行为)
|
||||
```html
|
||||
<code><a href="initialize.md">Initialize</a></code>
|
||||
```
|
||||
但在 `react-markdown` 中,通常链接包裹内容。如果链接内部包含 `code` 标签,`code` 标签的样式(`text-zinc-300`)可能会覆盖外层 `a` 标签的颜色样式(`text-blue-500`)。
|
||||
CSS 中,子元素的 `color` 属性通常会覆盖父元素的 `color`。这里 `code` 组件显式设置了 `text-zinc-300`(灰色),导致包裹在链接里的代码块显示为灰色,看起来像没有颜色的普通文本。
|
||||
|
||||
## 2. 修复方案
|
||||
|
||||
### 修改文件: `src/components/DocContent.tsx`
|
||||
|
||||
我们需要调整 `code` 组件的样式逻辑。如果 `code` 标签被包裹在 `a` 标签中,它应该继承父元素的颜色,而不是强制使用 `text-zinc-300`。
|
||||
但在 `react-markdown` 的组件渲染中,子组件很难直接知道父组件是谁。
|
||||
|
||||
**替代方案**:利用 CSS 的 `inherit` 特性或移除显式的颜色类。
|
||||
更稳妥的方法是:修改 `code` 组件的 `className`,让它不强制设置颜色,或者仅在非链接环境下设置颜色。
|
||||
或者,我们可以给 `a` 标签内的内容强制应用蓝色。
|
||||
|
||||
**最佳实践**:
|
||||
修改 `code` 组件的渲染,不再强制 `text-zinc-300`,而是使用一个默认颜色,但允许被父级覆盖。或者,我们在 `a` 组件中,给子元素强制颜色。
|
||||
|
||||
鉴于 `react-markdown` 的结构,最简单有效的方法是在 `code` 组件中移除 `text-zinc-300`,改为使用 `bg-zinc-800` 等背景色来区分代码块,字体颜色让其继承或使用更弱的默认值。
|
||||
但为了保持非链接代码块的灰色外观,我们可以尝试在 CSS (Tailwind) 中使用 `group` 或直接在 `a` 标签样式中强制子元素颜色。
|
||||
|
||||
**修改计划**:
|
||||
1. 修改 `a` 组件:添加 `group` 类。
|
||||
2. 修改 `code` 组件:添加 `group-hover:text-blue-400` (这可能不行,因为 code 不是 a 的直接子组件,react 渲染树中它们是分离的组件函数)。
|
||||
|
||||
**更直接的方案**:
|
||||
在 `code` 组件中,移除 `text-zinc-300`,改为 `text-inherit` 或者不设置颜色类(让其继承),然后在全局或父级容器设置默认文本颜色。
|
||||
或者,简单粗暴地:在 `a` 标签的 `className` 中添加 `[&>code]:text-blue-500` (Tailwind 任意变体语法),强制 `a` 标签下的 `code` 元素变成蓝色。
|
||||
|
||||
```typescript
|
||||
// 在 a 标签组件中
|
||||
className="text-blue-500 hover:text-blue-400 transition-colors cursor-pointer [&>code]:text-blue-500 [&>code]:hover:text-blue-400"
|
||||
```
|
||||
这样可以确保链接内的代码块也显示为蓝色。
|
||||
|
||||
## 3. 影响与验证
|
||||
- **影响**:所有被链接包裹的行内代码块(如 `[`Code`](link)`)都将变为蓝色,表明它们是可点击的链接。普通行内代码块保持原样(如果它们不在 a 标签内)。
|
||||
- **验证**:查看包含 `[`Initialize`](...)` 的文档,确认 "Initialize" 显示为蓝色且可点击。
|
||||
57
.trae/documents/fix_link_navigation_plan.md
Normal file
57
.trae/documents/fix_link_navigation_plan.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# 修复文档内链接跳转白屏问题计划
|
||||
|
||||
## 1. 问题分析
|
||||
用户反馈“点击链接直接白屏了”。这是因为在 Markdown 文档内容中,部分相对链接(例如 `[Some Doc](SomeDoc.md)`)没有以 `./` 或 `../` 开头。
|
||||
目前的 `DocContent.tsx` 组件在渲染 `<a>` 标签时,仅拦截了以 `./` 或 `../` 开头的链接。对于其他格式的本地链接,它会渲染为原生的 `<a href="SomeDoc.md">` 标签。
|
||||
在 Electron 或单页应用(SPA)中,点击原生链接会触发浏览器的默认导航行为,试图跳转到该文件路径,从而导致 React 应用被卸载(变成白屏或显示 404/纯文本)。
|
||||
|
||||
以前用户可以通过左侧树直接点击叶子节点查看文档,但在我们隐藏了叶子节点后,用户必须依赖文档内的链接进行导航,导致这个原本就存在的链接跳转问题暴露了出来。
|
||||
|
||||
## 2. 修改方案
|
||||
|
||||
### 修改文件: `src/components/DocContent.tsx`
|
||||
调整 Markdown 中 `<a>` 标签的渲染逻辑,拦截所有本地链接,阻止默认的页面跳转行为,并将其交给内部的 `onReferenceClick` 处理:
|
||||
|
||||
1. **识别内部链接**:
|
||||
判断 `href` 是否存在,且**不以** `http://`、`https://`、`mailto:` 或 `#` 开头。满足此条件的都视为内部文档链接。
|
||||
|
||||
2. **阻止默认行为并触发内部跳转**:
|
||||
对于内部链接,渲染为 `<a>` 标签,但在 `onClick` 事件中调用 `e.preventDefault()` 阻止白屏跳转,随后调用 `onReferenceClick(href)`。
|
||||
|
||||
```typescript
|
||||
a: ({ href, children }) => {
|
||||
// 判断是否是内部链接:非 http/https,非邮箱,非页内锚点
|
||||
const isInternal = href && !href.startsWith('http') && !href.startsWith('mailto:') && !href.startsWith('#');
|
||||
|
||||
if (isInternal) {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onReferenceClick(href);
|
||||
}}
|
||||
className="text-blue-500 hover:text-blue-400 transition-colors cursor-pointer"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
// 外部链接保留默认行为,但新窗口打开更安全
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-500 hover:text-blue-400 transition-colors"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
},
|
||||
```
|
||||
|
||||
## 3. 影响与验证
|
||||
- **影响**:所有的内部 Markdown 链接现在都会通过 React 状态更新右侧内容,而不会触发导致白屏的浏览器原生导航。
|
||||
- **验证**:保存修改后,点击文档内部的普通文件名链接(如 `SomeDoc.md`),页面将平滑切换到目标文档内容,不再出现白屏崩溃现象。
|
||||
39
.trae/documents/hide_leaf_nodes_plan.md
Normal file
39
.trae/documents/hide_leaf_nodes_plan.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# 隐藏层级树叶子节点实现计划
|
||||
|
||||
## 1. 目标概述
|
||||
在 API 文档界面的左侧层级树(`DocTree` 组件)中,隐藏所有的叶子节点(即具体的 Markdown 文件),使目录树仅展示文件夹(目录)结构。
|
||||
|
||||
## 2. 现状分析
|
||||
- 目前 `src/components/DocTree.tsx` 中的 `DocTree` 和 `TreeNode` 组件会无差别地渲染所有传入的节点(包含文件夹和文件)。
|
||||
- 树状图的连线UI(如 `├─` 和 `└─`)依赖于数组的索引和长度(`isLast` 属性)。如果仅在渲染时隐式返回 `null`,会导致连线计算错误。
|
||||
- 在当前的交互中,点击文件夹会默认打开与文件夹同名的 Markdown 索引文件(此时 `selectedPath` 是该 `.md` 文件的路径)。如果隐藏了文件节点,需要确保此时对应的文件夹能被正确高亮显示。
|
||||
|
||||
## 3. 具体修改方案
|
||||
|
||||
### 修改文件: `src/components/DocTree.tsx`
|
||||
|
||||
**1. 过滤子节点并修复连线逻辑 (在 `TreeNode` 组件中)**
|
||||
- 在渲染子节点之前,先过滤出 `isDir === true` 的节点:
|
||||
```typescript
|
||||
const visibleChildren = file.children?.filter(c => c.isDir)
|
||||
const showChildren = isDir && isExpanded && visibleChildren && visibleChildren.length > 0
|
||||
```
|
||||
- 在 `visibleChildren.map` 中渲染子节点,并基于 `visibleChildren.length` 来计算 `isLast`,确保树状图的连线完美闭合。
|
||||
|
||||
**2. 完善文件夹高亮逻辑 (在 `TreeNode` 组件中)**
|
||||
- 修改 `isSelected` 的判断,使得当子文件(与文件夹同名的索引文档)被选中时,该文件夹节点也能呈现高亮状态:
|
||||
```typescript
|
||||
const isSelected = selectedPath === file.relativePath ||
|
||||
(isDir && selectedPath === `${file.relativePath}/${file.name}.md`)
|
||||
```
|
||||
|
||||
**3. 过滤根节点 (在 `DocTree` 组件中)**
|
||||
- 在渲染根层级前过滤出文件夹:
|
||||
```typescript
|
||||
const visibleFiles = files.filter(f => f.isDir)
|
||||
```
|
||||
- 基于 `visibleFiles` 遍历并传递正确的 `isLast` 属性。
|
||||
|
||||
## 4. 影响与验证
|
||||
- **影响**:所有的独立 `.md` 文件将不再出现在左侧树中,用户需要通过点击目录(加载索引页)和页面内的文档链接来进行页面导航。
|
||||
- **验证**:保存修改后,左侧目录树将只显示文件夹图标/层级;连线正常;点击文件夹后,当前文件夹名称会变为高亮蓝色(`text-blue-400`)。
|
||||
Reference in New Issue
Block a user