Files
Verso/services/web/frontend/js/features/file-tree/components/file-tree-item/file-tree-item-menu-items.tsx
T
claude 065534819c
Build and Deploy Verso / deploy (push) Successful in 14m4s
Fix file tree refresh after convert and compiler sync on set-as-main
- Convert: backend now returns parentFolderId+isNew; frontend calls
  dispatchCreateDoc directly so the new file appears without a page refresh
- Set as main: infer compiler from file extension and POST both rootDocId
  and compiler together; updateProject propagates the change to the
  editor settings dropdown and project list immediately

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 07:50:02 +00:00

199 lines
6.4 KiB
TypeScript

import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import * as eventTracking from '../../../../infrastructure/event-tracking'
import { useProjectContext } from '@/shared/context/project-context'
import { postJSON } from '@/infrastructure/fetch-json'
import type { ProjectCompiler } from '@ol-types/project-settings'
import {
DropdownDivider,
DropdownItem,
} from '@/shared/components/dropdown/dropdown-menu'
import { useFileTreeActionable } from '../../contexts/file-tree-actionable'
import { useFileTreeSelectable } from '../../contexts/file-tree-selectable'
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
import { useFileTreeMainContext } from '../../contexts/file-tree-main'
import { findInTree } from '../../util/find-in-tree'
import useConvertDoc from '@/features/ide-react/hooks/use-convert-doc'
import getMeta from '@/utils/meta'
import { isValidTeXFile } from '@/main/is-valid-tex-file'
const COMPILER_BY_EXT: Record<string, ProjectCompiler> = {
tex: 'pdflatex',
rtex: 'pdflatex',
ltx: 'pdflatex',
rnw: 'pdflatex',
typ: 'typst',
qmd: 'quarto',
rmd: 'quarto',
}
function FileTreeItemMenuItems() {
const { t } = useTranslation()
const {
canRename,
canDelete,
canCreate,
startRenaming,
startDeleting,
startCreatingFolder,
startCreatingDocOrFile,
startUploadingDocOrFile,
downloadPath,
selectedFileName,
} = useFileTreeActionable()
const { project, projectId, updateProject } = useProjectContext()
const projectOwner = project?.owner?._id
const rootDocId = project?.rootDocId
const { fileTreeData, fileTreeReadOnly } = useFileTreeData()
const { selectedEntityIds } = useFileTreeSelectable()
const { contextMenuEntityId } = useFileTreeMainContext()
const selectedEntityId =
selectedEntityIds.size === 1 ? Array.from(selectedEntityIds)[0] : null
// Use context-menu-target entity for convert/set-as-main; falls back to selection
const convertEntityId = contextMenuEntityId ?? selectedEntityId
const convertEntity = convertEntityId
? findInTree(fileTreeData, convertEntityId)
: null
const isConvertableDoc = convertEntity?.type === 'doc'
const convertEntityName = convertEntity?.entity.name ?? null
const enablePandocConversions =
getMeta('ol-ExposedSettings')?.enablePandocConversions
const canConvertToTypst =
enablePandocConversions &&
!fileTreeReadOnly &&
isConvertableDoc &&
convertEntityName?.endsWith('.tex')
const canConvertToLatex =
enablePandocConversions &&
!fileTreeReadOnly &&
isConvertableDoc &&
convertEntityName?.endsWith('.typ')
const canShowSetAsMain =
!fileTreeReadOnly &&
isConvertableDoc &&
!!convertEntityId &&
convertEntityId !== rootDocId &&
!!convertEntityName &&
isValidTeXFile(convertEntityName)
const handleSetAsMain = useCallback(async () => {
if (!convertEntityId || !convertEntityName) return
const ext = convertEntityName.split('.').pop()?.toLowerCase() ?? ''
const newCompiler = COMPILER_BY_EXT[ext]
const body: Record<string, string> = { rootDocId: convertEntityId }
if (newCompiler && newCompiler !== project?.compiler) body.compiler = newCompiler
await postJSON(`/project/${projectId}/settings`, { body })
const update: Record<string, string> = { rootDocId: convertEntityId }
if (newCompiler) update.compiler = newCompiler
updateProject(update)
}, [convertEntityId, convertEntityName, project, projectId, updateProject])
const { convert: convertToTypst } = useConvertDoc('typst', convertEntityId)
const { convert: convertToLatex } = useConvertDoc('latex', convertEntityId)
const downloadWithAnalytics = useCallback(() => {
// we are only interested in downloads of bib files WRT analytics, for the purposes of promoting the tpr integrations
if (selectedFileName?.endsWith('.bib')) {
eventTracking.sendMB('download-bib-file', { projectOwner })
}
}, [selectedFileName, projectOwner])
const createWithAnalytics = useCallback(() => {
eventTracking.sendMB('new-file-click', { location: 'file-menu' })
startCreatingDocOrFile()
}, [startCreatingDocOrFile])
const uploadWithAnalytics = useCallback(() => {
eventTracking.sendMB('upload-click', { location: 'file-menu' })
startUploadingDocOrFile()
}, [startUploadingDocOrFile])
return (
<>
{canRename ? (
<li role="none">
<DropdownItem onClick={startRenaming}>{t('rename')}</DropdownItem>
</li>
) : null}
{downloadPath ? (
<li role="none">
<DropdownItem
href={downloadPath}
onClick={downloadWithAnalytics}
download={selectedFileName ?? undefined}
>
{t('download')}
</DropdownItem>
</li>
) : null}
{canShowSetAsMain ? (
<>
<DropdownDivider />
<li role="none">
<DropdownItem onClick={handleSetAsMain}>
{t('set_as_main_document')}
</DropdownItem>
</li>
</>
) : null}
{(canConvertToTypst || canConvertToLatex) ? (
<>
<DropdownDivider />
{canConvertToTypst && (
<li role="none">
<DropdownItem onClick={convertToTypst}>
{t('convert_to_typst')}
</DropdownItem>
</li>
)}
{canConvertToLatex && (
<li role="none">
<DropdownItem onClick={convertToLatex}>
{t('convert_to_latex')}
</DropdownItem>
</li>
)}
</>
) : null}
{canDelete ? (
<>
<DropdownDivider />
<li role="none">
<DropdownItem onClick={startDeleting}>{t('delete')}</DropdownItem>
</li>
</>
) : null}
{canCreate ? (
<>
<DropdownDivider />
<li role="none">
<DropdownItem onClick={createWithAnalytics}>
{t('new_file')}
</DropdownItem>
</li>
<li role="none">
<DropdownItem onClick={startCreatingFolder}>
{t('new_folder')}
</DropdownItem>
</li>
<li role="none">
<DropdownItem onClick={uploadWithAnalytics}>
{t('upload')}
</DropdownItem>
</li>
</>
) : null}
</>
)
}
export default FileTreeItemMenuItems