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:
2026-03-19 12:44:08 +08:00
parent e003fe6513
commit 58a83f445a
1012 changed files with 56880 additions and 22 deletions

View File

@@ -107,9 +107,10 @@ export const ApiDocViewer = ({ onDocsPathChange, showAddModal, onCloseAddModal }
const handleFolderClick = useCallback((folderPath: string) => {
const parts = folderPath.split('/')
const basename = parts[parts.length - 1]
const sameNameDoc = externalDocs.find(d =>
d.relativePath.endsWith(`/${basename}.md`)
)
// 使用精确路径匹配:目标文件必须在 folderPath 目录下,且名为 basename.md
const targetPath = `${folderPath}/${basename}.md`
const sameNameDoc = externalDocs.find(d => d.relativePath === targetPath)
if (sameNameDoc) {
setSelectedPath(sameNameDoc.relativePath)
@@ -128,16 +129,50 @@ export const ApiDocViewer = ({ onDocsPathChange, showAddModal, onCloseAddModal }
}
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 (!selectedPath) return
// 1. 获取当前文档所在的目录
const currentDir = selectedPath.split('/').slice(0, -1)
// 2. 解析目标路径
let targetParts: string[] = []
if (href.startsWith('/')) {
// 绝对路径(相对于文档根目录)
const pathStr = href.slice(1)
targetParts = pathStr ? pathStr.split('/') : []
} else {
// 相对路径处理
targetParts = [...currentDir]
const hrefParts = href.split('/')
for (const part of hrefParts) {
if (part === '.') continue
if (part === '..') {
targetParts.pop()
} else {
targetParts.push(part)
}
}
}
let targetPath = targetParts.join('/')
// 3. 规范化目标路径(确保包含 .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])
}, [externalDocs, selectedPath])
return (
<div className="flex h-full bg-[#1e1e1e]">

View File

@@ -18,18 +18,29 @@ export const DocContent = ({ content, onReferenceClick }: DocContentProps) => {
const components: Components = {
a: ({ href, children }) => {
if (href && (href.startsWith('./') || href.startsWith('../'))) {
const isInternal = href && !href.startsWith('http') && !href.startsWith('mailto:') && !href.startsWith('#');
if (isInternal) {
return (
<button
onClick={() => onReferenceClick(href)}
className="text-blue-500 hover:text-blue-400 transition-colors"
<a
href={href}
onClick={(e) => {
e.preventDefault();
onReferenceClick(href);
}}
className="text-inherit hover:text-zinc-300 transition-colors cursor-pointer font-bold underline decoration-zinc-500 underline-offset-4"
>
{children}
</button>
</a>
)
}
return (
<a href={href} className="text-blue-500 hover:text-blue-400 transition-colors">
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="text-inherit hover:text-zinc-300 transition-colors font-bold underline decoration-zinc-500 underline-offset-4"
>
{children}
</a>
)

View File

@@ -35,7 +35,8 @@ const TreeNode = React.memo(({
}: TreeNodeProps) => {
const isDir = file.isDir
const isExpanded = expandedSet.has(file.relativePath)
const isSelected = selectedPath === file.relativePath
const isSelected = selectedPath === file.relativePath ||
(isDir && selectedPath === `${file.relativePath}/${file.name}.md`)
const handleClick = () => {
if (isDir) {
@@ -59,7 +60,8 @@ const TreeNode = React.memo(({
return level * 3 + 3
}
const showChildren = isDir && isExpanded && file.children
const visibleChildren = file.children?.filter(c => c.isDir)
const showChildren = isDir && isExpanded && visibleChildren && visibleChildren.length > 0
return (
<>
@@ -88,12 +90,12 @@ const TreeNode = React.memo(({
</span>
</div>
{showChildren && file.children!.map((child, idx) => (
{showChildren && visibleChildren!.map((child, idx) => (
<TreeNode
key={child.relativePath}
file={child}
level={level + 1}
isLast={idx === file.children!.length - 1}
isLast={idx === visibleChildren!.length - 1}
ancestorsLast={[...ancestorsLast, isLast]}
selectedPath={selectedPath}
onSelect={onSelect}
@@ -129,14 +131,16 @@ export const DocTree = ({ files, selectedPath, onSelect, onFolderClick }: DocTre
})
}
const visibleFiles = files.filter(f => f.isDir)
return (
<div className="select-none w-full h-full overflow-auto py-2 px-1">
{files.map((file, idx) => (
{visibleFiles.map((file, idx) => (
<TreeNode
key={file.relativePath}
file={file}
level={0}
isLast={idx === files.length - 1}
isLast={idx === visibleFiles.length - 1}
ancestorsLast={[]}
selectedPath={selectedPath}
onSelect={onSelect}