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:
@@ -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]">
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user