Files
Verso/services/web/frontend/js/features/file-tree/components/file-tree-item/file-tree-item-menu-items.tsx
T
claude b0b389dc4c
Build and Deploy Verso / deploy (push) Successful in 14m56s
feat: set-as-main for .typ/.qmd; fix compiler filter; fix ZIP import compiler
Set as main document (context menu):
- canSetRootDocId used selectedEntityIds so .typ/.qmd files couldn't be
  set as main via right-click on an unselected file.
  Now computed locally from contextMenuEntityId (same pattern as convert)
  using isValidTeXFile which already covers .typ, .qmd, .tex etc.

Compiler filter (editor settings):
- docs?.find(id) could return undefined due to ID format mismatch,
  causing all engines to show as available for non-LaTeX projects.
  Added findInTree fallback so the root doc name is always resolved.

ZIP import compiler:
- Projects created from ZIP always got defaultLatexCompiler ('quarto')
  regardless of content.
- findRootDocFileFromDirectory now also searches for .typ and .qmd root
  files after finding no .tex file.
- ProjectUploadManager now infers the compiler from the root doc
  extension and sets it on the project after import.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 21:56:39 +00:00

182 lines
5.7 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 {
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'
import { syncRootDocId } from '../../util/sync-mutation'
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) return
await syncRootDocId(projectId, convertEntityId)
updateProject({ rootDocId: convertEntityId })
}, [convertEntityId, 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