adding web changes of Export HTML (#34117)

GitOrigin-RevId: 804c576faefebfc6683a0363b45372e66a43d8fc
This commit is contained in:
Davinder Singh
2026-06-04 12:08:48 +01:00
committed by Copybot
parent fc2abf5b24
commit 6ce36a2606
10 changed files with 107 additions and 3 deletions
@@ -19,6 +19,7 @@ const { z, zz, parseReq } = Validation
const SUPPORTED_CONVERSION_TYPES = new Map([
['docx', 'docx'],
['markdown', 'zip'],
['html', 'zip'],
])
const exportProjectConversionSchema = z.object({
@@ -483,6 +483,7 @@ const _ProjectController = {
'export-docx',
'sharing-updates',
'export-markdown',
'export-html',
'command-palette',
'overleaf-library',
'compile-timeout-cta',
@@ -654,6 +654,7 @@
"expires_on": "",
"explore_plans": "",
"export_as_docx": "",
"export_as_html": "",
"export_as_markdown": "",
"export_csv": "",
"export_project_to_github": "",
@@ -916,6 +917,7 @@
"how_to_insert_images": "",
"how_we_use_your_data": "",
"how_we_use_your_data_explanation": "",
"html_export_feedback_message": "",
"i_confirm_am_student": "",
"i_want_to_add_a_po_number": "",
"i_want_to_stay": "",
@@ -71,6 +71,20 @@ const ExportDocumentSuccessToast = ({ data }: { data?: any }) => {
]}
/>
)
} else if (type === 'html') {
return (
<Trans
i18nKey="html_export_feedback_message"
components={[
// eslint-disable-next-line react/jsx-key, jsx-a11y/anchor-has-content
<a
href="https://forms.gle/nBUVGqPYwLcWHSmt5"
target="_BLANK"
rel="noopener noreferrer"
/>,
]}
/>
)
} else {
return (
<Trans
@@ -160,7 +174,9 @@ export const hidePreparingExportToast = (handle: string) => {
)
}
export const showExportDocumentSuccess = (type: 'docx' | 'markdown') => {
export const showExportDocumentSuccess = (
type: 'docx' | 'markdown' | 'html'
) => {
window.dispatchEvent(
new CustomEvent('ide:show-toast', {
detail: { key: 'export-document:success', type },
@@ -9,7 +9,7 @@ import { useEditorManagerContext } from '@/features/ide-react/context/editor-man
type ExportProjectWithConversionProps = {
featureFlag?: string
conversionType: 'docx' | 'markdown'
conversionType: 'docx' | 'markdown' | 'html'
label: string
menuBarId: string
}
@@ -96,6 +96,7 @@ export const ToolbarMenuBar = () => {
'download-pdf',
'export-as-docx',
'export-as-markdown',
'export-as-html',
],
},
],
@@ -89,6 +89,12 @@ export const ToolbarProjectTitle = () => {
label={t('export_as_markdown')}
menuBarId="export-as-markdown"
/>
<ExportProjectWithConversionButton
featureFlag="export-html"
conversionType="html"
label={t('export_as_html')}
menuBarId="export-as-html"
/>
<DropdownDivider />
<DuplicateProject />
<OLDropdownMenuItem
@@ -16,7 +16,7 @@ import { OpenDocuments } from '../editor/open-documents'
const SLOW_CONVERSION_THRESHOLD = 2000
export default function useConvertProject(
type: 'docx' | 'markdown',
type: 'docx' | 'markdown' | 'html',
openDocs: OpenDocuments,
getRootDocInfo: () => RootDocInfo
) {
+2
View File
@@ -859,6 +859,7 @@
"explore_all_plans": "Explore all plans",
"explore_plans": "Explore plans",
"export_as_docx": "Export as Word document (.docx)",
"export_as_html": "Export as HTML (.html)",
"export_as_markdown": "Export as Markdown (.md)",
"export_csv": "Export CSV",
"export_project_to_github": "Export Project to GitHub",
@@ -1181,6 +1182,7 @@
"how_to_insert_images": "How to insert images",
"how_we_use_your_data": "How we use your data",
"how_we_use_your_data_explanation": "<0>Please help us continue to improve Overleaf by answering a few quick questions. Your answers will help us and our corporate group understand more about our user base. We may use this information to improve your Overleaf experience, for example by providing personalized onboarding, upgrade prompts, help suggestions, and tailored marketing communications (if youve opted-in to receive them).</0><1>For more details on how we use your personal data, please see our <0>Privacy Notice</0>.</1>",
"html_export_feedback_message": "Exporting as HTML is a new feature. <0>Let us know what you think</0>",
"hundreds_templates_info": "Produce beautiful documents starting from our gallery of LaTeX templates for journals, conferences, theses, reports, CVs and much more.",
"i_confirm_am_student": "I confirm that I am currently a student.",
"i_want_to_add_a_po_number": "I want to add a PO number",
@@ -541,6 +541,81 @@ describe('ProjectDownloadsController', function () {
})
})
describe('with type=html', function () {
beforeEach(async function (ctx) {
ctx.projectId = '5e9b1c2a3b4c5d6e7f8a9b0c'
ctx.userId = 'test-user-id'
ctx.projectName = 'My Test Project'
ctx.exportStream = { pipe: sinon.stub() }
ctx.contentLength = 9876
ctx.req.params = { Project_id: ctx.projectId, type: 'html' }
ctx.req.session = { user: { _id: ctx.userId } }
ctx.req.query = {}
ctx.req.ip = '192.168.1.1'
ctx.res.attachment = sinon.stub().returns(ctx.res)
ctx.SessionManager.getLoggedInUserId.returns(ctx.userId)
ctx.ProjectGetter.promises.getProject.resolves({
name: ctx.projectName,
})
ctx.DocumentConversionManager.promises.convertProjectToDocument.resolves(
{
conversionId: '12345678-1234-4234-8234-123456789012',
buildId: '0123456789a-0123456789abcdef',
clsiServerId: 'clsi-server-1',
file: 'output.zip',
}
)
ctx.DocumentConversionManager.promises.streamConvertedProjectDocument.resolves(
{
stream: ctx.exportStream,
contentLength: ctx.contentLength,
}
)
await ctx.ProjectDownloadsController.exportProjectConversion(
ctx.req,
ctx.res,
ctx.next
)
})
it('should call convertProjectToDocument with the html type', function (ctx) {
sinon.assert.calledWith(
ctx.DocumentConversionManager.promises.convertProjectToDocument,
ctx.projectId,
ctx.userId,
'html'
)
})
it('should set the attachment filename with .zip extension', function (ctx) {
sinon.assert.calledWith(ctx.res.attachment, 'My_Test_Project.zip')
})
it('should add an audit log entry for html export', function (ctx) {
sinon.assert.calledWith(
ctx.ProjectAuditLogHandler.addEntryInBackground,
ctx.projectId,
'project-exported-html',
ctx.userId,
ctx.req.ip
)
})
it('should record the action via Metrics with html type', function (ctx) {
ctx.Metrics.inc
.calledWith('document-exports', 1, { type: 'html' })
.should.equal(true)
})
it('should stream the document to the response', function (ctx) {
sinon.assert.calledWith(ctx.pipeline, ctx.exportStream, ctx.res)
})
})
describe('when conversion fails with a DocumentConversionError', function () {
beforeEach(async function (ctx) {
ctx.projectId = '5e9b1c2a3b4c5d6e7f8a9b0c'