Files
Verso/services/web/frontend/js/features/ide-react/hooks/use-convert-project.ts
T
claudeandClaude Sonnet 4.6 ac2315bc8e
Build and Deploy Verso / deploy (push) Successful in 9m50s
feat: per-file Convert in explorer menu + fix export success toast
- Add "Convert to Typst (.typ)" in the file tree context menu for .tex
  docs, and "Convert to LaTeX (.tex)" for .typ docs. Clicking runs pandoc
  on the file content and creates the converted file in the same folder.
- New backend endpoint POST /project/:id/doc/:id/convert/:type that reads
  the doc from document-updater, runs pandoc directly, and creates the
  result via ProjectEntityUpdateHandler (file tree updates via socket).
- Rewrite the export success toast for typst and latex conversions: no
  more link to /contact, replaced with a plain warning that errors are
  expected (pandoc does not support all constructs).
- Add i18n keys: convert_to_typst, convert_to_latex,
  typst_export_feedback_message, latex_export_feedback_message (EN + FR)
  and all four to extracted-translations.json.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 15:18:31 +00:00

67 lines
2.3 KiB
TypeScript

import { FetchError, getJSON } from '@/infrastructure/fetch-json'
import { useLocation } from '@/shared/hooks/use-location'
import { debugConsole } from '@/utils/debugging'
import { useProjectContext } from '@/shared/context/project-context'
import { useCallback } from 'react'
import {
hideExportDocumentError,
hidePreparingExportToast,
showExportDocumentError,
showExportDocumentSuccess,
showPreparingExportToast,
} from '../components/toolbar/export-document-toasts'
import { RootDocInfo } from '@/shared/hooks/use-root-doc'
import { OpenDocuments } from '../editor/open-documents'
const SLOW_CONVERSION_THRESHOLD = 2000
export default function useConvertProject(
type: 'docx' | 'markdown' | 'html' | 'typst' | 'latex',
openDocs: OpenDocuments,
getRootDocInfo: () => RootDocInfo
) {
const { projectId } = useProjectContext()
const location = useLocation()
return useCallback(async () => {
let handle: string | undefined
const toastTimer = setTimeout(() => {
handle = showPreparingExportToast()
}, SLOW_CONVERSION_THRESHOLD)
const hidePreparingToast = () => {
clearTimeout(toastTimer)
if (handle) hidePreparingExportToast(handle)
}
const url = new URL(window.location.origin)
url.pathname = `/project/${projectId}/download/conversion/${type}`
url.searchParams.set('responseFormat', 'json')
const { rootResourcePath } = getRootDocInfo()
url.searchParams.set('rootResourcePath', rootResourcePath)
hideExportDocumentError()
try {
await openDocs.awaitBufferedOps(AbortSignal.timeout(10_000))
const response = await getJSON(url.href)
hidePreparingToast()
const { downloadUrl } = response
if (downloadUrl) {
const url = new URL(downloadUrl, window.location.origin)
location.assign(url.toString())
showExportDocumentSuccess(type as 'docx' | 'markdown' | 'html' | 'typst' | 'latex')
} else {
showExportDocumentError()
}
} catch (error) {
hidePreparingToast()
let errorMessage
if (
error instanceof FetchError &&
error.response?.status === 422 &&
error.data?.error
) {
errorMessage = error.data.error
}
showExportDocumentError(errorMessage)
debugConsole.error(error)
}
}, [projectId, type, getRootDocInfo, openDocs, location])
}