mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 21:23:45 +01:00
Merge pull request #35855 from overleaf/ds-unable-to-sync-modal-improved
[WEB] Adding improved version of the unable to sync modal GitOrigin-RevId: 1cfe2b3a2e8417028339efddb18385a5224e50c3
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import { memo, useCallback, useMemo } from 'react'
|
||||
import { diffWordsWithSpace } from 'diff'
|
||||
|
||||
type DiffViewerProps = {
|
||||
baseContent: string
|
||||
targetContent: string
|
||||
}
|
||||
|
||||
// Renders an inline diff between two document versions using <ins> and <del>
|
||||
// tags. This mirrors the DOM-based rendering approach used by the workbench
|
||||
// CodeDiff component (modules/workbench/frontend/js/components/code-diff.tsx).
|
||||
// TODO: refactor both to share a common diff renderer from a shared location.
|
||||
function DiffViewer({ baseContent, targetContent }: DiffViewerProps) {
|
||||
const changes = useMemo(
|
||||
() => diffWordsWithSpace(baseContent, targetContent),
|
||||
[baseContent, targetContent]
|
||||
)
|
||||
|
||||
const createDiff = useCallback(
|
||||
(element: HTMLDivElement) => {
|
||||
if (!element) return
|
||||
element.replaceChildren()
|
||||
|
||||
for (const change of changes) {
|
||||
if (change.added) {
|
||||
const ins = document.createElement('ins')
|
||||
ins.textContent = change.value
|
||||
element.append(ins)
|
||||
} else if (change.removed) {
|
||||
const del = document.createElement('del')
|
||||
del.textContent = change.value
|
||||
element.append(del)
|
||||
} else {
|
||||
const text = document.createTextNode(change.value)
|
||||
element.append(text)
|
||||
}
|
||||
}
|
||||
},
|
||||
[changes]
|
||||
)
|
||||
|
||||
return <div className="diff-container" ref={createDiff} />
|
||||
}
|
||||
|
||||
export default memo(DiffViewer)
|
||||
+79
-11
@@ -1,5 +1,5 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { memo, useCallback } from 'react'
|
||||
import { memo, useCallback, useState } from 'react'
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
import {
|
||||
OLModal,
|
||||
@@ -8,42 +8,110 @@ import {
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/shared/components/ol/ol-modal'
|
||||
import { downloadFileContent } from '@/utils/download-file'
|
||||
import DiffViewer from './diff-viewer'
|
||||
import getMeta from '@/utils/meta'
|
||||
import { uploadBatch } from '@/infrastructure/batch-file-uploader'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
|
||||
export type UnableToSyncModalProps = {
|
||||
editorContent: string
|
||||
baseContent: string
|
||||
targetContent: string
|
||||
docName: string | null
|
||||
rootFolderId: string | undefined
|
||||
show: boolean
|
||||
onHide: () => void
|
||||
}
|
||||
|
||||
// Generates the offline copy filename with a timestamp to avoid duplicates.
|
||||
// e.g. main.tex -> main(offline-2026-07-27-14-07).tex
|
||||
function buildOfflineFilename(docName: string | null): string {
|
||||
const now = new Date()
|
||||
const stamp = [
|
||||
now.getFullYear(),
|
||||
String(now.getMonth() + 1).padStart(2, '0'),
|
||||
String(now.getDate()).padStart(2, '0'),
|
||||
String(now.getHours()).padStart(2, '0'),
|
||||
String(now.getMinutes()).padStart(2, '0'),
|
||||
].join('-')
|
||||
const suffix = `(offline-${stamp})`
|
||||
if (!docName) {
|
||||
return `main${suffix}.tex`
|
||||
}
|
||||
const lastDot = docName.lastIndexOf('.')
|
||||
if (lastDot === -1) {
|
||||
return `${docName}${suffix}`
|
||||
}
|
||||
return `${docName.slice(0, lastDot)}${suffix}${docName.slice(lastDot)}`
|
||||
}
|
||||
|
||||
function UnableToSyncModal({
|
||||
editorContent,
|
||||
baseContent,
|
||||
targetContent,
|
||||
docName,
|
||||
rootFolderId,
|
||||
show,
|
||||
onHide,
|
||||
}: UnableToSyncModalProps) {
|
||||
const { t } = useTranslation()
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
const handleDownload = useCallback(() => {
|
||||
downloadFileContent(editorContent, docName ?? 'document.txt')
|
||||
}, [editorContent, docName])
|
||||
const handleSaveAsNewFile = useCallback(async () => {
|
||||
const projectId = getMeta('ol-project_id')
|
||||
const filename = buildOfflineFilename(docName)
|
||||
|
||||
setSaving(true)
|
||||
try {
|
||||
if (!rootFolderId) {
|
||||
throw new Error('rootFolderId not available')
|
||||
}
|
||||
const [result] = await uploadBatch(
|
||||
[
|
||||
{
|
||||
file: new Blob([targetContent], { type: 'text/plain' }),
|
||||
name: filename,
|
||||
},
|
||||
],
|
||||
{ projectId, folderId: rootFolderId }
|
||||
)
|
||||
if (result.status === 'error') {
|
||||
throw new Error(result.error)
|
||||
}
|
||||
onHide()
|
||||
} catch (error) {
|
||||
debugConsole.error('Failed to save offline changes as new file', error)
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}, [docName, targetContent, rootFolderId, onHide])
|
||||
|
||||
return (
|
||||
<OLModal
|
||||
show={show}
|
||||
onHide={onHide}
|
||||
className="unable-to-sync-modal"
|
||||
backdrop="static"
|
||||
keyboard={false}
|
||||
centered
|
||||
>
|
||||
<OLModalHeader>
|
||||
<OLModalHeader closeButton={false}>
|
||||
<OLModalTitle>{t('your_offline_edits_couldnt_be_synced')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
<OLModalBody>{t('offline_edits_couldnt_be_synced_detail')}</OLModalBody>
|
||||
<OLModalBody>
|
||||
<p>{t('offline_edits_couldnt_combine')}</p>
|
||||
<DiffViewer baseContent={baseContent} targetContent={targetContent} />
|
||||
<p className="mt-2">{t('offline_save_explanation')}</p>
|
||||
</OLModalBody>
|
||||
<OLModalFooter>
|
||||
<OLButton variant="primary" onClick={handleDownload}>
|
||||
{t('download_local_version')}
|
||||
<OLButton variant="danger-ghost" onClick={onHide}>
|
||||
{t('discard_changes')}
|
||||
</OLButton>
|
||||
<OLButton
|
||||
variant="primary"
|
||||
onClick={handleSaveAsNewFile}
|
||||
isLoading={saving}
|
||||
loadingLabel={t('saving')}
|
||||
>
|
||||
{t('save_as_new_file')}
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
|
||||
Reference in New Issue
Block a user