mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 14:13:43 +01:00
Merge pull request #33341 from overleaf/mj-two-step-export-web
[web] Add two-step pandoc conversion download GitOrigin-RevId: 093f435a497a7583d2b4d23558415cc442f84553
This commit is contained in:
committed by
Copybot
parent
64d706f114
commit
62d92b70dd
@@ -40,7 +40,7 @@ let toastCounter = 1
|
||||
|
||||
export const GlobalToasts = memo(function GlobalToasts() {
|
||||
const [toasts, setToasts] = useState<
|
||||
{ component: ReactElement; id: string }[]
|
||||
{ component: ReactElement; id: string; handle?: string }[]
|
||||
>([])
|
||||
|
||||
const removeToast = useCallback((id: string) => {
|
||||
@@ -72,13 +72,13 @@ export const GlobalToasts = memo(function GlobalToasts() {
|
||||
)
|
||||
|
||||
const addToast = useCallback(
|
||||
(key: string, data?: any) => {
|
||||
(key: string, handle?: string, data?: any) => {
|
||||
const id = `toast-${toastCounter++}`
|
||||
const component = createToast(id, key, data)
|
||||
if (!component) {
|
||||
return
|
||||
}
|
||||
setToasts(current => [...current, { id, component }])
|
||||
setToasts(current => [...current, { id, handle, component }])
|
||||
},
|
||||
[createToast]
|
||||
)
|
||||
@@ -89,14 +89,25 @@ export const GlobalToasts = memo(function GlobalToasts() {
|
||||
debugConsole.error('No key provided for toast')
|
||||
return
|
||||
}
|
||||
const { key, ...rest } = event.detail
|
||||
addToast(key, rest)
|
||||
const { key, handle, ...rest } = event.detail
|
||||
addToast(key, handle, rest)
|
||||
},
|
||||
[addToast]
|
||||
)
|
||||
|
||||
useEventListener('ide:show-toast', showToastListener)
|
||||
|
||||
const dismissToastListener = useCallback((event: CustomEvent) => {
|
||||
const { handle } = event.detail || {}
|
||||
if (!handle) {
|
||||
debugConsole.error('No handle provided for dismissing toast')
|
||||
return
|
||||
}
|
||||
setToasts(current => current.filter(toast => toast.handle !== handle))
|
||||
}, [])
|
||||
|
||||
useEventListener('ide:dismiss-toast', dismissToastListener)
|
||||
|
||||
return (
|
||||
<OLToastContainer className="global-toasts">
|
||||
{toasts.map(({ component, id }) => (
|
||||
|
||||
+9
-16
@@ -9,6 +9,7 @@ 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()
|
||||
@@ -105,11 +106,11 @@ export const DownloadProjectPDF = () => {
|
||||
|
||||
export const ExportProjectDocx = () => {
|
||||
const { t } = useTranslation()
|
||||
const { projectId } = useProjectContext()
|
||||
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
|
||||
@@ -120,12 +121,12 @@ export const ExportProjectDocx = () => {
|
||||
? [
|
||||
{
|
||||
id: 'export-as-docx',
|
||||
href: `/project/${projectId}/download/conversion/docx`,
|
||||
handler: downloadConversion,
|
||||
label: t('export_as_docx'),
|
||||
},
|
||||
]
|
||||
: [],
|
||||
[t, showExportDocx, projectId]
|
||||
[t, showExportDocx, downloadConversion]
|
||||
)
|
||||
|
||||
if (!showExportDocx) {
|
||||
@@ -133,11 +134,7 @@ export const ExportProjectDocx = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<OLDropdownMenuItem
|
||||
href={`/project/${projectId}/download/conversion/docx`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<OLDropdownMenuItem onClick={downloadConversion}>
|
||||
{t('export_as_docx')}
|
||||
</OLDropdownMenuItem>
|
||||
)
|
||||
@@ -145,11 +142,11 @@ export const ExportProjectDocx = () => {
|
||||
|
||||
export const ExportProjectMarkdown = () => {
|
||||
const { t } = useTranslation()
|
||||
const { projectId } = useProjectContext()
|
||||
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
|
||||
@@ -160,12 +157,12 @@ export const ExportProjectMarkdown = () => {
|
||||
? [
|
||||
{
|
||||
id: 'export-as-markdown',
|
||||
href: `/project/${projectId}/download/conversion/markdown`,
|
||||
handler: downloadConversion,
|
||||
label: t('export_as_markdown'),
|
||||
},
|
||||
]
|
||||
: [],
|
||||
[t, showExportMarkdown, projectId]
|
||||
[t, showExportMarkdown, downloadConversion]
|
||||
)
|
||||
|
||||
if (!showExportMarkdown) {
|
||||
@@ -173,11 +170,7 @@ export const ExportProjectMarkdown = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<OLDropdownMenuItem
|
||||
href={`/project/${projectId}/download/conversion/markdown`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<OLDropdownMenuItem onClick={downloadConversion}>
|
||||
{t('export_as_markdown')}
|
||||
</OLDropdownMenuItem>
|
||||
)
|
||||
|
||||
+55
-2
@@ -1,9 +1,27 @@
|
||||
import { GlobalToastGeneratorEntry } from '@/features/ide-react/components/global-toasts'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
const PreparingExportToast = () => {
|
||||
const { t } = useTranslation()
|
||||
return <span>{t('preparing_for_export')}</span>
|
||||
}
|
||||
|
||||
const ExportDocumentErrorToast = () => {
|
||||
const { t } = useTranslation()
|
||||
return <span>{t('export_document_error')}</span>
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
<b>{t('we_couldnt_export_this_document')}</b>
|
||||
</p>
|
||||
<Trans
|
||||
i18nKey="the_document_contains_formatting_we_werent_able_to_convert_contact_support_if_you_need_help"
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key, jsx-a11y/anchor-has-content
|
||||
<a href="/contact" target="_BLANK" rel="noopener noreferrer" />,
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const generators: GlobalToastGeneratorEntry[] = [
|
||||
@@ -17,6 +35,41 @@ const generators: GlobalToastGeneratorEntry[] = [
|
||||
isDismissible: true,
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: 'export-document:preparing',
|
||||
generator: () => ({
|
||||
content: <PreparingExportToast />,
|
||||
type: 'info',
|
||||
autoHide: false,
|
||||
isDismissible: true,
|
||||
}),
|
||||
},
|
||||
]
|
||||
|
||||
export default generators
|
||||
|
||||
export const showExportDocumentError = () => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('ide:show-toast', {
|
||||
detail: { key: 'export-document:error' },
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export const showPreparingExportToast = () => {
|
||||
const handle = `export-document-preparing-${Date.now()}`
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('ide:show-toast', {
|
||||
detail: { key: 'export-document:preparing', handle },
|
||||
})
|
||||
)
|
||||
return handle
|
||||
}
|
||||
|
||||
export const hidePreparingExportToast = (handle: string) => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('ide:dismiss-toast', {
|
||||
detail: { key: 'export-document:preparing', handle },
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { 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 {
|
||||
hidePreparingExportToast,
|
||||
showExportDocumentError,
|
||||
showPreparingExportToast,
|
||||
} from '../components/toolbar/export-document-toasts'
|
||||
|
||||
const SLOW_CONVERSION_THRESHOLD = 2000
|
||||
|
||||
export default function useConvertProject(type: 'docx' | 'markdown') {
|
||||
const { projectId } = useProjectContext()
|
||||
const location = useLocation()
|
||||
const triggerConversion = useCallback(async () => {
|
||||
let handle: string | undefined
|
||||
const toastTimer = setTimeout(() => {
|
||||
handle = showPreparingExportToast()
|
||||
}, SLOW_CONVERSION_THRESHOLD)
|
||||
const hidePreparingToast = () => {
|
||||
clearTimeout(toastTimer)
|
||||
if (handle) hidePreparingExportToast(handle)
|
||||
}
|
||||
try {
|
||||
const response = await getJSON(
|
||||
`/project/${projectId}/download/conversion/${type}?responseFormat=json`
|
||||
)
|
||||
hidePreparingToast()
|
||||
const { downloadUrl } = response
|
||||
if (downloadUrl) {
|
||||
const url = new URL(downloadUrl, window.location.origin)
|
||||
location.assign(url.toString())
|
||||
}
|
||||
} catch (error) {
|
||||
hidePreparingToast()
|
||||
showExportDocumentError()
|
||||
debugConsole.error(error)
|
||||
}
|
||||
}, [projectId, type, location])
|
||||
|
||||
return triggerConversion
|
||||
}
|
||||
Reference in New Issue
Block a user