From 8a7f9ad6e8c03a7aad5438c5f986afbad8d05cc6 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Wed, 18 Mar 2026 20:13:30 +0800 Subject: [PATCH] fix: folder click shows overview doc, add same-name filter, move + to header --- src/App.tsx | 20 ++++++++--- src/components/ApiDocViewer.tsx | 61 +++++++++++++++++++++------------ src/components/DocContent.tsx | 14 ++++---- src/components/DocTree.tsx | 46 ++++++++++++------------- 4 files changed, 86 insertions(+), 55 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 6e8c120..9c235a3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { FileText, Box } from 'lucide-react'; +import { FileText, Box, Plus } from 'lucide-react'; import { ApiDocViewer } from './components/ApiDocViewer'; import BlueprintPage from './components/blueprint/BlueprintPage'; import { config } from './config'; @@ -26,6 +26,7 @@ declare global { function App() { const [currentPage, setCurrentPage] = useState('docs'); const [docsPath, setDocsPath] = useState(''); + const [showAddModal, setShowAddModal] = useState(false); useEffect(() => { document.title = config.projectName; @@ -41,7 +42,7 @@ function App() { +
{currentPage === 'docs' ? ( - + setShowAddModal(false)} + /> ) : ( )} diff --git a/src/components/ApiDocViewer.tsx b/src/components/ApiDocViewer.tsx index aca4716..fd0b537 100644 --- a/src/components/ApiDocViewer.tsx +++ b/src/components/ApiDocViewer.tsx @@ -1,5 +1,5 @@ -import { useState, useCallback } from 'react' -import { X, Plus, AlertCircle } from 'lucide-react' +import { useState, useCallback, useEffect } from 'react' +import { X, AlertCircle } from 'lucide-react' import { DocTree } from './DocTree' import { DocContent } from './DocContent' import { buildFileTree } from '@/lib/parser' @@ -14,9 +14,11 @@ interface ExternalDoc { interface ApiDocViewerProps { onDocsPathChange?: (path: string) => void; + showAddModal?: boolean; + onCloseAddModal?: () => void; } -export const ApiDocViewer = ({ onDocsPathChange }: ApiDocViewerProps) => { +export const ApiDocViewer = ({ onDocsPathChange, showAddModal, onCloseAddModal }: ApiDocViewerProps) => { const [fileTree, setFileTree] = useState([]) const [selectedPath, setSelectedPath] = useState() const [currentContent, setCurrentContent] = useState('') @@ -26,6 +28,13 @@ export const ApiDocViewer = ({ onDocsPathChange }: ApiDocViewerProps) => { const [isLoading, setIsLoading] = useState(false) const [errorMsg, setErrorMsg] = useState(null) + useEffect(() => { + if (showAddModal) { + setShowModal(true) + setErrorMsg(null) + } + }, [showAddModal]) + const loadDocsFromPath = async (basePath: string): Promise => { if (!basePath) { setErrorMsg('请输入文档路径') @@ -61,7 +70,17 @@ export const ApiDocViewer = ({ onDocsPathChange }: ApiDocViewerProps) => { } setExternalDocs(docs) - const fileList = docs.map(d => d.relativePath.replace(/^api\//, '')) + + // 过滤掉与父文件夹同名的 md 文件,只用于目录树显示 + const filteredDocs = docs.filter(doc => { + const parts = doc.relativePath.split('/') + if (parts.length < 2) return true + const filename = parts[parts.length - 1].replace(/\.md$/, '') + const parentFolder = parts[parts.length - 2] + return filename !== parentFolder + }) + + const fileList = filteredDocs.map(d => d.relativePath.replace(/^api\//, '')) const tree = buildFileTree(fileList, '/') setFileTree(tree) @@ -79,12 +98,20 @@ export const ApiDocViewer = ({ onDocsPathChange }: ApiDocViewerProps) => { } const handleSelect = useCallback((file: DocFile) => { - if (!file.isDir) { - setSelectedPath(file.relativePath) - const doc = externalDocs.find(d => d.relativePath === file.relativePath) - if (doc) { - setCurrentContent(doc.content) - } + setSelectedPath(file.relativePath) + const doc = externalDocs.find(d => d.relativePath === file.relativePath) + if (doc) { + setCurrentContent(doc.content) + } + }, [externalDocs]) + + const handleFolderClick = useCallback((folderPath: string) => { + const sameNamePath = `${folderPath}/${folderPath}.md` + const sameNameDoc = externalDocs.find(d => d.relativePath === sameNamePath) + + if (sameNameDoc) { + setSelectedPath(sameNameDoc.relativePath.replace(/^api\//, '')) + setCurrentContent(sameNameDoc.content) } }, [externalDocs]) @@ -93,6 +120,7 @@ export const ApiDocViewer = ({ onDocsPathChange }: ApiDocViewerProps) => { const success = await loadDocsFromPath(docsPath.trim()) if (success) { setShowModal(false) + onCloseAddModal?.() onDocsPathChange?.(docsPath.trim()) } } @@ -112,24 +140,15 @@ export const ApiDocViewer = ({ onDocsPathChange }: ApiDocViewerProps) => { return (
diff --git a/src/components/DocContent.tsx b/src/components/DocContent.tsx index ebb985d..f9c1f53 100644 --- a/src/components/DocContent.tsx +++ b/src/components/DocContent.tsx @@ -44,19 +44,19 @@ export const DocContent = ({ content, onReferenceClick }: DocContentProps) => { ) } return ( - + {children} ) }, pre: ({ children }) => ( -
+      
         {children}
       
), table: ({ children }) => (
- +
{children}
@@ -67,12 +67,12 @@ export const DocContent = ({ content, onReferenceClick }: DocContentProps) => { ), th: ({ children }) => ( - + {children} ), td: ({ children }) => ( - + {children} ), @@ -82,7 +82,7 @@ export const DocContent = ({ content, onReferenceClick }: DocContentProps) => { ), h1: ({ children }) => ( -

+

{children}

), @@ -102,7 +102,7 @@ export const DocContent = ({ content, onReferenceClick }: DocContentProps) => { ), p: ({ children }) => ( -

+

{children}

), diff --git a/src/components/DocTree.tsx b/src/components/DocTree.tsx index 034c12c..c87ba8d 100644 --- a/src/components/DocTree.tsx +++ b/src/components/DocTree.tsx @@ -7,6 +7,7 @@ interface DocTreeProps { files: DocFile[] selectedPath?: string onSelect: (file: DocFile) => void + onFolderClick?: (folderPath: string) => void } interface TreeNodeProps { @@ -16,6 +17,7 @@ interface TreeNodeProps { ancestorsLast: boolean[] selectedPath?: string onSelect: (file: DocFile) => void + onFolderClick?: (folderPath: string) => void expandedSet: Set onToggle: (path: string) => void } @@ -27,6 +29,7 @@ const TreeNode = React.memo(({ ancestorsLast, selectedPath, onSelect, + onFolderClick, expandedSet, onToggle, }: TreeNodeProps) => { @@ -36,32 +39,24 @@ const TreeNode = React.memo(({ const handleClick = () => { if (isDir) { + onFolderClick?.(file.relativePath) onToggle(file.relativePath) } else { onSelect(file) } } - const renderIndent = () => { - const parts: React.ReactNode[] = [] - + const getIndent = (): string => { + let result = '' for (let i = 0; i < level; i++) { - const showLine = !ancestorsLast[i] - parts.push( - - {showLine ? '│' : ' '} - - ) + result += ancestorsLast[i] ? ' ' : '│ ' } - - const connector = isLast ? '└──' : '├──' - parts.push( - - {connector} - - ) - - return parts + result += isLast ? '└─ ' : '├─ ' + return result + } + + const getIndentWidth = (): number => { + return level * 3 + 3 } const showChildren = isDir && isExpanded && file.children @@ -71,7 +66,7 @@ const TreeNode = React.memo(({
- - {renderIndent()} + + {getIndent()} {getDisplayName(file.name)} @@ -99,6 +97,7 @@ const TreeNode = React.memo(({ ancestorsLast={[...ancestorsLast, isLast]} selectedPath={selectedPath} onSelect={onSelect} + onFolderClick={onFolderClick} expandedSet={expandedSet} onToggle={onToggle} /> @@ -109,7 +108,7 @@ const TreeNode = React.memo(({ TreeNode.displayName = 'TreeNode' -export const DocTree = ({ files, selectedPath, onSelect }: DocTreeProps) => { +export const DocTree = ({ files, selectedPath, onSelect, onFolderClick }: DocTreeProps) => { const [expandedSet, setExpandedSet] = useState>(() => { const set = new Set() files.forEach(f => { @@ -141,6 +140,7 @@ export const DocTree = ({ files, selectedPath, onSelect }: DocTreeProps) => { ancestorsLast={[]} selectedPath={selectedPath} onSelect={onSelect} + onFolderClick={onFolderClick} expandedSet={expandedSet} onToggle={handleToggle} />