mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
3297506021
* Revert "Revert "Tw reference manager access"" This reverts commit 55f3512f254e136d8a239bb5d9ca39f0d04720fd. * added conditional to check whether file linked data exists * created new translation key for refresh error * Fix for misleading reference manager access error to ensure users are aware only original importer of reference file can refresh it. If original importer, then user is prompted to relink provider account. Previous PR and conversation #13618. GitOrigin-RevId: 85232c1559c6d2068effec50e31b8fa30bf53c89
292 lines
8.4 KiB
JavaScript
292 lines
8.4 KiB
JavaScript
import { useState, useCallback } from 'react'
|
|
import PropTypes from 'prop-types'
|
|
import { Trans, useTranslation } from 'react-i18next'
|
|
|
|
import Icon from '../../../shared/components/icon'
|
|
import { formatTime, relativeDate } from '../../utils/format-date'
|
|
import { postJSON } from '../../../infrastructure/fetch-json'
|
|
import { useEditorContext } from '../../../shared/context/editor-context'
|
|
import { useProjectContext } from '../../../shared/context/project-context'
|
|
import { useUserContext } from '../../../shared/context/user-context'
|
|
import { capitalize } from 'lodash'
|
|
|
|
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
|
|
import useAbortController from '../../../shared/hooks/use-abort-controller'
|
|
import { LinkedFileIcon } from './file-view-icons'
|
|
const tprLinkedFileInfo = importOverleafModules('tprLinkedFileInfo')
|
|
const tprLinkedFileRefreshError = importOverleafModules(
|
|
'tprLinkedFileRefreshError'
|
|
)
|
|
|
|
const MAX_URL_LENGTH = 60
|
|
const FRONT_OF_URL_LENGTH = 35
|
|
const FILLER = '...'
|
|
const TAIL_OF_URL_LENGTH = MAX_URL_LENGTH - FRONT_OF_URL_LENGTH - FILLER.length
|
|
|
|
function shortenedUrl(url) {
|
|
if (!url) {
|
|
return
|
|
}
|
|
if (url.length > MAX_URL_LENGTH) {
|
|
const front = url.slice(0, FRONT_OF_URL_LENGTH)
|
|
const tail = url.slice(url.length - TAIL_OF_URL_LENGTH)
|
|
return front + FILLER + tail
|
|
}
|
|
return url
|
|
}
|
|
|
|
export default function FileViewHeader({ file, storeReferencesKeys }) {
|
|
const { _id: projectId } = useProjectContext({
|
|
_id: PropTypes.string.isRequired,
|
|
})
|
|
const { permissionsLevel } = useEditorContext({
|
|
permissionsLevel: PropTypes.string,
|
|
})
|
|
const { id: userId } = useUserContext()
|
|
const { t } = useTranslation()
|
|
|
|
const [refreshing, setRefreshing] = useState(false)
|
|
const [refreshError, setRefreshError] = useState(null)
|
|
|
|
const { signal } = useAbortController()
|
|
|
|
let fileInfo
|
|
let isImporter
|
|
if (file.linkedFileData) {
|
|
isImporter = file.linkedFileData.importer_id === userId
|
|
if (file.linkedFileData.provider === 'url') {
|
|
fileInfo = (
|
|
<div>
|
|
<UrlProvider file={file} />
|
|
</div>
|
|
)
|
|
} else if (file.linkedFileData.provider === 'project_file') {
|
|
fileInfo = (
|
|
<div>
|
|
<ProjectFilePathProvider file={file} />
|
|
</div>
|
|
)
|
|
} else if (file.linkedFileData.provider === 'project_output_file') {
|
|
fileInfo = (
|
|
<div>
|
|
<ProjectOutputFileProvider file={file} />
|
|
</div>
|
|
)
|
|
}
|
|
}
|
|
|
|
const refreshFile = useCallback(() => {
|
|
setRefreshing(true)
|
|
// Replacement of the file handled by the file tree
|
|
window.expectingLinkedFileRefreshedSocketFor = file.name
|
|
postJSON(`/project/${projectId}/linked_file/${file.id}/refresh`, { signal })
|
|
.then(() => {
|
|
setRefreshing(false)
|
|
})
|
|
.catch(err => {
|
|
setRefreshing(false)
|
|
setRefreshError(err.data?.message || err.message)
|
|
})
|
|
.finally(() => {
|
|
if (
|
|
file.linkedFileData.provider === 'mendeley' ||
|
|
file.linkedFileData.provider === 'zotero' ||
|
|
file.name.match(/^.*\.bib$/)
|
|
) {
|
|
reindexReferences()
|
|
}
|
|
})
|
|
|
|
function reindexReferences() {
|
|
const opts = {
|
|
body: { shouldBroadcast: true },
|
|
}
|
|
|
|
postJSON(`/project/${projectId}/references/indexAll`, opts)
|
|
.then(response => {
|
|
// Later updated by the socket but also updated here for immediate use
|
|
storeReferencesKeys(response.keys)
|
|
})
|
|
.catch(error => {
|
|
console.log(error)
|
|
})
|
|
}
|
|
}, [file, projectId, signal, storeReferencesKeys])
|
|
|
|
return (
|
|
<div>
|
|
{file.linkedFileData && fileInfo}
|
|
{file.linkedFileData &&
|
|
tprLinkedFileInfo.map(({ import: { LinkedFileInfo }, path }) => (
|
|
<LinkedFileInfo key={path} file={file} />
|
|
))}
|
|
{file.linkedFileData && permissionsLevel !== 'readOnly' && (
|
|
<button
|
|
className={`btn ${isImporter ? 'btn-primary' : 'btn-secondary'}`}
|
|
onClick={refreshFile}
|
|
disabled={refreshing || !isImporter}
|
|
>
|
|
<Icon type="refresh" spin={refreshing} fw />
|
|
<span>{refreshing ? t('refreshing') + '...' : t('refresh')}</span>
|
|
</button>
|
|
)}
|
|
|
|
<a
|
|
download
|
|
href={`/project/${projectId}/file/${file.id}`}
|
|
className="btn btn-secondary-info btn-secondary"
|
|
>
|
|
<Icon type="download" fw />
|
|
|
|
<span>{t('download')}</span>
|
|
</a>
|
|
{file.linkedFileData && !isImporter && (
|
|
<div className="row">
|
|
<div className="alert">
|
|
{t('only_importer_can_refresh', {
|
|
provider: capitalize(file.linkedFileData.provider),
|
|
})}
|
|
</div>
|
|
</div>
|
|
)}
|
|
{refreshError && (
|
|
<div className="row">
|
|
<br />
|
|
<div
|
|
className="alert alert-danger col-md-10 col-md-offset-1"
|
|
style={{ display: 'flex', alignItems: 'center', gap: '10px' }}
|
|
>
|
|
<div>
|
|
{t('something_not_right')}!
|
|
{tprLinkedFileRefreshError.map(
|
|
({ import: { LinkedFileRefreshError }, path }) => (
|
|
<LinkedFileRefreshError key={path} file={file} />
|
|
)
|
|
)}
|
|
</div>
|
|
<div className="text-center">
|
|
<button className="btn btn-danger">
|
|
<a
|
|
href="/user/settings"
|
|
target="_blank"
|
|
style={{ fontWeight: 'bold', textDecoration: 'none' }}
|
|
>
|
|
{t('go_to_settings')}
|
|
</a>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
FileViewHeader.propTypes = {
|
|
file: PropTypes.shape({
|
|
id: PropTypes.string,
|
|
name: PropTypes.string,
|
|
linkedFileData: PropTypes.object,
|
|
}).isRequired,
|
|
storeReferencesKeys: PropTypes.func.isRequired,
|
|
}
|
|
|
|
function UrlProvider({ file }) {
|
|
return (
|
|
<p>
|
|
<LinkedFileIcon />
|
|
|
|
<Trans
|
|
i18nKey="imported_from_external_provider_at_date"
|
|
components={
|
|
/* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
|
|
[<a href={file.linkedFileData.url} />]
|
|
}
|
|
values={{
|
|
shortenedUrl: shortenedUrl(file.linkedFileData.url),
|
|
formattedDate: formatTime(file.created),
|
|
relativeDate: relativeDate(file.created),
|
|
}}
|
|
/>
|
|
</p>
|
|
)
|
|
}
|
|
|
|
UrlProvider.propTypes = {
|
|
file: PropTypes.shape({
|
|
linkedFileData: PropTypes.object,
|
|
created: PropTypes.string,
|
|
}).isRequired,
|
|
}
|
|
|
|
function ProjectFilePathProvider({ file }) {
|
|
/* eslint-disable jsx-a11y/anchor-has-content, react/jsx-key */
|
|
return (
|
|
<p>
|
|
<LinkedFileIcon />
|
|
|
|
<Trans
|
|
i18nKey="imported_from_another_project_at_date"
|
|
components={
|
|
file.linkedFileData.v1_source_doc_id
|
|
? [<span />]
|
|
: [
|
|
<a
|
|
href={`/project/${file.linkedFileData.source_project_id}`}
|
|
target="_blank"
|
|
rel="noopener"
|
|
/>,
|
|
]
|
|
}
|
|
values={{
|
|
sourceEntityPath: file.linkedFileData.source_entity_path.slice(1),
|
|
formattedDate: formatTime(file.created),
|
|
relativeDate: relativeDate(file.created),
|
|
}}
|
|
/>
|
|
</p>
|
|
/* esline-enable jsx-a11y/anchor-has-content, react/jsx-key */
|
|
)
|
|
}
|
|
|
|
ProjectFilePathProvider.propTypes = {
|
|
file: PropTypes.shape({
|
|
linkedFileData: PropTypes.object,
|
|
created: PropTypes.string,
|
|
}).isRequired,
|
|
}
|
|
|
|
function ProjectOutputFileProvider({ file }) {
|
|
return (
|
|
<p>
|
|
<LinkedFileIcon />
|
|
|
|
<Trans
|
|
i18nKey="imported_from_the_output_of_another_project_at_date"
|
|
components={
|
|
file.linkedFileData.v1_source_doc_id
|
|
? [<span />]
|
|
: [
|
|
<a
|
|
href={`/project/${file.linkedFileData.source_project_id}`}
|
|
target="_blank"
|
|
rel="noopener"
|
|
/>,
|
|
]
|
|
}
|
|
values={{
|
|
sourceOutputFilePath: file.linkedFileData.source_output_file_path,
|
|
formattedDate: formatTime(file.created),
|
|
relativeDate: relativeDate(file.created),
|
|
}}
|
|
/>
|
|
</p>
|
|
)
|
|
}
|
|
|
|
ProjectOutputFileProvider.propTypes = {
|
|
file: PropTypes.shape({
|
|
linkedFileData: PropTypes.object,
|
|
created: PropTypes.string,
|
|
}).isRequired,
|
|
}
|