Files
Verso/services/web/frontend/js/features/file-tree/components/file-tree-item/file-tree-item-inner.tsx
T
claude ff7de70a61
Build and Deploy Verso / deploy (push) Successful in 21m44s
fix: per-file convert — DuplicateNameError + first-click no-op
Two bugs:
1. Converting when output already exists threw DuplicateNameError (400).
   Now overwrites existing doc via setDocument instead of failing.

2. Right-clicking an unselected file left contextMenuEntityId null,
   so the first click on Convert silently did nothing. Added
   contextMenuEntityId to FileTreeMainContext, set it on right-click
   and on the … button click; FileTreeItemMenuItems now uses it for
   the convert hooks rather than relying on selectedEntityIds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 20:35:50 +00:00

159 lines
4.3 KiB
TypeScript

import { ReactNode, useEffect, useRef } from 'react'
import classNames from 'classnames'
import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
import { useFileTreeMainContext } from '../../contexts/file-tree-main'
import { useDraggable } from '../../contexts/file-tree-draggable'
import FileTreeItemName from './file-tree-item-name'
import FileTreeItemMenu from './file-tree-item-menu'
import SplitTestBadge from '@/shared/components/split-test-badge'
import { useFeatureFlag } from '@/shared/context/split-test-context'
import { useFileTreeSelectable } from '../../contexts/file-tree-selectable'
import { useFileTreeActionable } from '../../contexts/file-tree-actionable'
import { useDragDropManager } from 'react-dnd'
function FileTreeItemInner({
id,
name,
type,
isSelected,
icons,
onClick,
}: {
id: string
name: string
type: string
isSelected: boolean
icons?: ReactNode
onClick?: () => void
}) {
const { fileTreeReadOnly } = useFileTreeData()
const { setContextMenuCoords, setContextMenuEntityId } =
useFileTreeMainContext()
const { isRenaming } = useFileTreeActionable()
const { selectedEntityIds } = useFileTreeSelectable()
const hasMenu =
!fileTreeReadOnly && isSelected && selectedEntityIds.size === 1
const showBibBadge =
useFeatureFlag('bibtex-visual-editor') &&
type !== 'folder' &&
name.toLowerCase().endsWith('.bib')
const { dragRef, setIsDraggable } = useDraggable(id)
const dragDropItem = useDragDropManager().getMonitor().getItem()
const itemRef = useRef<HTMLDivElement | null>(null)
useEffect(() => {
const item = itemRef.current
if (isSelected && item) {
// we're delaying scrolling due to a race condition with other elements,
// mainly the Outline, being resized inside the same panel, causing the
// FileTree to have its viewport shrinked after the selected item is
// scrolled into the view, hiding it again.
// See `left-pane-resize-all` in `file-tree-controller` for more information.
setTimeout(() => {
if (item) {
scrollIntoViewIfNeeded(item, {
scrollMode: 'if-needed',
})
}
}, 100)
}
}, [isSelected, itemRef])
function handleContextMenu(ev: React.MouseEvent<HTMLDivElement>) {
if (ev.shiftKey) {
setContextMenuCoords(null)
return
}
ev.preventDefault()
setContextMenuEntityId(id)
setContextMenuCoords({
top: ev.pageY,
left: ev.pageX,
})
}
return (
<div
className={classNames('entity', {
'file-tree-entity-dragging': dragDropItem?.draggedEntityIds?.has(id),
})}
role="presentation"
ref={dragRef}
draggable={!isRenaming}
onContextMenu={handleContextMenu}
data-file-id={id}
data-file-type={type}
>
<div
className={classNames('entity-name', 'entity-name-react', {
'file-tree-has-bib-badge': showBibBadge,
})}
role="presentation"
ref={itemRef}
>
<FileTreeItemIconsAndName
name={name}
isSelected={isSelected}
icons={icons}
onClick={onClick}
setIsDraggable={setIsDraggable}
/>
{showBibBadge && (
<div className="file-tree-bib-badge text-white">
<SplitTestBadge
splitTestName="bibtex-visual-editor"
displayOnVariants={['enabled']}
/>
</div>
)}
{hasMenu ? <FileTreeItemMenu id={id} name={name} /> : null}
</div>
</div>
)
}
const FileTreeItemIconsAndName = ({
name,
isSelected,
icons,
onClick,
setIsDraggable,
}: {
name: string
isSelected: boolean
icons?: ReactNode
onClick?: () => void
setIsDraggable: (isDraggable: boolean) => void
}) => {
return onClick ? (
<button className="file-tree-entity-button" onClick={onClick}>
{icons}
<FileTreeItemName
name={name}
isSelected={isSelected}
setIsDraggable={setIsDraggable}
/>
</button>
) : (
<div className="file-tree-entity-details">
{icons}
<FileTreeItemName
name={name}
isSelected={isSelected}
setIsDraggable={setIsDraggable}
/>
</div>
)
}
export default FileTreeItemInner