Merge pull request #33560 from overleaf/mj-conversion-cleanup

[clsi+web] Small cleanups and improvements to conversions / exports

GitOrigin-RevId: 300adfbb91e89f754ee7f835db792ccb50b27613
This commit is contained in:
Mathias Jakobsen
2026-05-12 08:06:17 +00:00
committed by Copybot
parent 62d92b70dd
commit 6b28a4ee5a
9 changed files with 381 additions and 433 deletions
@@ -7,9 +7,6 @@ import { useProjectContext } from '@/shared/context/project-context'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
import getMeta from '@/utils/meta'
import { useFeatureFlag } from '@/shared/context/split-test-context'
import useConvertProject from '../../hooks/use-convert-project'
export const DownloadProjectZip = () => {
const { t } = useTranslation()
@@ -103,75 +100,3 @@ export const DownloadProjectPDF = () => {
return button
}
}
export const ExportProjectDocx = () => {
const { t } = useTranslation()
const exportDocxEnabled = useFeatureFlag('export-docx')
const enablePandocConversions =
getMeta('ol-ExposedSettings')?.enablePandocConversions
const anonymous = getMeta('ol-anonymous')
const downloadConversion = useConvertProject('docx')
const showExportDocx =
exportDocxEnabled && enablePandocConversions && !anonymous
useCommandProvider(
() =>
showExportDocx
? [
{
id: 'export-as-docx',
handler: downloadConversion,
label: t('export_as_docx'),
},
]
: [],
[t, showExportDocx, downloadConversion]
)
if (!showExportDocx) {
return null
}
return (
<OLDropdownMenuItem onClick={downloadConversion}>
{t('export_as_docx')}
</OLDropdownMenuItem>
)
}
export const ExportProjectMarkdown = () => {
const { t } = useTranslation()
const exportMarkdownEnabled = useFeatureFlag('export-markdown')
const enablePandocConversions =
getMeta('ol-ExposedSettings')?.enablePandocConversions
const anonymous = getMeta('ol-anonymous')
const downloadConversion = useConvertProject('markdown')
const showExportMarkdown =
exportMarkdownEnabled && enablePandocConversions && !anonymous
useCommandProvider(
() =>
showExportMarkdown
? [
{
id: 'export-as-markdown',
handler: downloadConversion,
label: t('export_as_markdown'),
},
]
: [],
[t, showExportMarkdown, downloadConversion]
)
if (!showExportMarkdown) {
return null
}
return (
<OLDropdownMenuItem onClick={downloadConversion}>
{t('export_as_markdown')}
</OLDropdownMenuItem>
)
}
@@ -24,6 +24,49 @@ const ExportDocumentErrorToast = () => {
)
}
const ExportDocumentSuccessToast = ({ data }: { data?: any }) => {
const type = data?.type
if (type === 'docx') {
return (
<Trans
i18nKey="docx_export_feedback_message"
components={[
// eslint-disable-next-line react/jsx-key, jsx-a11y/anchor-has-content
<a
href="https://forms.gle/Fg4BUXV2yv61hStX8"
target="_BLANK"
rel="noopener noreferrer"
/>,
]}
/>
)
} else if (type === 'markdown') {
return (
<Trans
i18nKey="markdown_export_feedback_message"
components={[
// eslint-disable-next-line react/jsx-key, jsx-a11y/anchor-has-content
<a
href="https://forms.gle/wc43zEukeqpec9mAA"
target="_BLANK"
rel="noopener noreferrer"
/>,
]}
/>
)
} else {
return (
<Trans
i18nKey="generic_export_feedback_message"
components={[
// eslint-disable-next-line react/jsx-key, jsx-a11y/anchor-has-content
<a href="/contact" target="_BLANK" rel="noopener noreferrer" />,
]}
/>
)
}
}
const generators: GlobalToastGeneratorEntry[] = [
{
key: 'export-document:error',
@@ -44,6 +87,16 @@ const generators: GlobalToastGeneratorEntry[] = [
isDismissible: true,
}),
},
{
key: 'export-document:success',
generator: (data: any) => ({
content: <ExportDocumentSuccessToast data={data} />,
type: 'success',
autoHide: true,
delay: 45000,
isDismissible: true,
}),
},
]
export default generators
@@ -73,3 +126,11 @@ export const hidePreparingExportToast = (handle: string) => {
})
)
}
export const showExportDocumentSuccess = (type: 'docx' | 'markdown') => {
window.dispatchEvent(
new CustomEvent('ide:show-toast', {
detail: { key: 'export-document:success', type },
})
)
}
@@ -0,0 +1,51 @@
import getMeta from '@/utils/meta'
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
import { FC } from 'react'
import useConvertProject from '../../hooks/use-convert-project'
import { useCommandProvider } from '../../hooks/use-command-provider'
import OLDropdownMenuItem from '@/shared/components/ol/ol-dropdown-menu-item'
type ExportProjectWithConversionProps = {
featureFlag?: string
conversionType: 'docx' | 'markdown'
label: string
menuBarId: string
}
export const ExportProjectWithConversionButton: FC<
ExportProjectWithConversionProps
> = ({ featureFlag, conversionType, label, menuBarId }) => {
const splitTestEnabledIfNeeded = featureFlag
? isSplitTestEnabled(featureFlag)
: true
const enablePandocConversions =
getMeta('ol-ExposedSettings')?.enablePandocConversions
const anonymous = getMeta('ol-anonymous')
const downloadConversion = useConvertProject(conversionType)
const showExportButton =
splitTestEnabledIfNeeded && enablePandocConversions && !anonymous
useCommandProvider(
() =>
showExportButton
? [
{
id: menuBarId,
handler: downloadConversion,
label,
},
]
: [],
[showExportButton, downloadConversion, label, menuBarId]
)
if (!showExportButton) {
return null
}
return (
<OLDropdownMenuItem onClick={downloadConversion}>
{label}
</OLDropdownMenuItem>
)
}
@@ -10,16 +10,12 @@ import { useTranslation } from 'react-i18next'
import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
import { useEditorContext } from '@/shared/context/editor-context'
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
import {
DownloadProjectPDF,
DownloadProjectZip,
ExportProjectDocx,
ExportProjectMarkdown,
} from './download-project'
import { DownloadProjectPDF, DownloadProjectZip } from './download-project'
import { useCallback, useState } from 'react'
import OLDropdownMenuItem from '@/shared/components/ol/ol-dropdown-menu-item'
import EditableLabel from './editable-label'
import { DuplicateProject } from './duplicate-project'
import { ExportProjectWithConversionButton } from './export-project-with-conversion-button'
const [publishModalModules] = importOverleafModules('publishModal')
const SubmitProjectButton = publishModalModules?.import.NewPublishDropdownButton
@@ -81,8 +77,18 @@ export const ToolbarProjectTitle = () => {
)}
<DownloadProjectPDF />
<DownloadProjectZip />
<ExportProjectDocx />
<ExportProjectMarkdown />
<ExportProjectWithConversionButton
featureFlag="export-docx"
conversionType="docx"
label={t('export_as_docx')}
menuBarId="export-as-docx"
/>
<ExportProjectWithConversionButton
featureFlag="export-markdown"
conversionType="markdown"
label={t('export_as_markdown')}
menuBarId="export-as-markdown"
/>
<DropdownDivider />
<DuplicateProject />
<OLDropdownMenuItem
@@ -6,6 +6,7 @@ import { useCallback } from 'react'
import {
hidePreparingExportToast,
showExportDocumentError,
showExportDocumentSuccess,
showPreparingExportToast,
} from '../components/toolbar/export-document-toasts'
@@ -32,6 +33,9 @@ export default function useConvertProject(type: 'docx' | 'markdown') {
if (downloadUrl) {
const url = new URL(downloadUrl, window.location.origin)
location.assign(url.toString())
showExportDocumentSuccess(type)
} else {
showExportDocumentError()
}
} catch (error) {
hidePreparingToast()