mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 02:18:08 +08:00
feat(skill-editor): render flat search result list in file tree
Replace the tree-filtered search with a flat list that shows icon + name on the left and parent path on the right, matching the Figma design. Clicking a file opens its tab; clicking a folder clears the search and reveals the folder in the tree.
This commit is contained in:
@ -28,6 +28,7 @@ import { useSkillTreeCollaboration } from '../hooks/use-skill-tree-collaboration
|
|||||||
import { useSyncTreeWithActiveTab } from '../hooks/use-sync-tree-with-active-tab'
|
import { useSyncTreeWithActiveTab } from '../hooks/use-sync-tree-with-active-tab'
|
||||||
import { isDescendantOf } from '../utils/tree-utils'
|
import { isDescendantOf } from '../utils/tree-utils'
|
||||||
import DragActionTooltip from './drag-action-tooltip'
|
import DragActionTooltip from './drag-action-tooltip'
|
||||||
|
import SearchResultList from './search-result-list'
|
||||||
import TreeContextMenu from './tree-context-menu'
|
import TreeContextMenu from './tree-context-menu'
|
||||||
import TreeNode from './tree-node'
|
import TreeNode from './tree-node'
|
||||||
import UploadStatusTooltip from './upload-status-tooltip'
|
import UploadStatusTooltip from './upload-status-tooltip'
|
||||||
@ -366,6 +367,17 @@ const FileTree = ({ className }: FileTreeProps) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (searchTerm) {
|
||||||
|
return (
|
||||||
|
<div className={cn('flex min-h-[150px] flex-1 flex-col overflow-y-auto', className)}>
|
||||||
|
<SearchResultList
|
||||||
|
searchTerm={searchTerm}
|
||||||
|
treeChildren={treeChildren as AppAssetTreeView[]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -0,0 +1,80 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import type { AppAssetTreeView } from '@/types/app-asset'
|
||||||
|
import { useCallback, useMemo } from 'react'
|
||||||
|
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||||
|
import { cn } from '@/utils/classnames'
|
||||||
|
import { flattenMatchingNodes, getAncestorIds } from '../utils/tree-utils'
|
||||||
|
import { TreeNodeIcon } from './tree-node-icon'
|
||||||
|
|
||||||
|
type SearchResultListProps = {
|
||||||
|
searchTerm: string
|
||||||
|
treeChildren: AppAssetTreeView[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const SearchResultList = ({ searchTerm, treeChildren }: SearchResultListProps) => {
|
||||||
|
const activeTabId = useStore(s => s.activeTabId)
|
||||||
|
const storeApi = useWorkflowStore()
|
||||||
|
|
||||||
|
const results = useMemo(
|
||||||
|
() => flattenMatchingNodes(treeChildren, searchTerm),
|
||||||
|
[treeChildren, searchTerm],
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleClick = useCallback((node: AppAssetTreeView) => {
|
||||||
|
if (node.node_type === 'file') {
|
||||||
|
storeApi.getState().openTab(node.id, { pinned: true })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const ancestors = getAncestorIds(node.id, treeChildren)
|
||||||
|
storeApi.getState().revealFile([...ancestors, node.id])
|
||||||
|
storeApi.getState().setFileTreeSearchTerm('')
|
||||||
|
}
|
||||||
|
}, [storeApi, treeChildren])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-px p-1">
|
||||||
|
{results.map(({ node, parentPath }) => (
|
||||||
|
<div
|
||||||
|
key={node.id}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
className={cn(
|
||||||
|
'flex h-6 w-full cursor-pointer items-center rounded-md px-2',
|
||||||
|
'hover:bg-state-base-hover',
|
||||||
|
activeTabId === node.id && 'bg-state-base-active',
|
||||||
|
)}
|
||||||
|
onClick={() => handleClick(node)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault()
|
||||||
|
handleClick(node)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex min-w-0 flex-1 items-center">
|
||||||
|
<div className="flex size-5 shrink-0 items-center justify-center">
|
||||||
|
<TreeNodeIcon
|
||||||
|
isFolder={node.node_type === 'folder'}
|
||||||
|
isOpen={false}
|
||||||
|
fileName={node.name}
|
||||||
|
extension={node.extension}
|
||||||
|
isDirty={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="min-w-0 truncate px-1 py-0.5 text-[13px] font-normal leading-4 text-text-secondary">
|
||||||
|
{node.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{parentPath && (
|
||||||
|
<span className="system-xs-regular shrink-0 text-text-tertiary">
|
||||||
|
{parentPath}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SearchResultList
|
||||||
@ -199,6 +199,37 @@ function insertDraftNodeAtParent(
|
|||||||
return { nodes: inserted ? nextNodes : nodes, inserted }
|
return { nodes: inserted ? nextNodes : nodes, inserted }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type FlatSearchResult = {
|
||||||
|
node: AppAssetTreeView
|
||||||
|
parentPath: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function flattenMatchingNodes(
|
||||||
|
nodes: AppAssetTreeView[],
|
||||||
|
searchTerm: string,
|
||||||
|
): FlatSearchResult[] {
|
||||||
|
if (!searchTerm)
|
||||||
|
return []
|
||||||
|
|
||||||
|
const results: FlatSearchResult[] = []
|
||||||
|
const lowerTerm = searchTerm.toLowerCase()
|
||||||
|
|
||||||
|
function traverse(nodeList: AppAssetTreeView[]): void {
|
||||||
|
for (const node of nodeList) {
|
||||||
|
if (node.name.toLowerCase().includes(lowerTerm)) {
|
||||||
|
const lastSlash = node.path.lastIndexOf('/')
|
||||||
|
const parentPath = lastSlash > 0 ? node.path.slice(1, lastSlash) : ''
|
||||||
|
results.push({ node, parentPath })
|
||||||
|
}
|
||||||
|
if (node.children && node.children.length > 0)
|
||||||
|
traverse(node.children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
traverse(nodes)
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
export function insertDraftTreeNode(
|
export function insertDraftTreeNode(
|
||||||
nodes: AppAssetTreeView[],
|
nodes: AppAssetTreeView[],
|
||||||
parentId: string | null,
|
parentId: string | null,
|
||||||
|
|||||||
Reference in New Issue
Block a user