Merge pull request #39763 from nextcloud/backport/39171/stable27

This commit is contained in:
Julius Härtl 2023-08-09 18:18:37 +02:00 committed by GitHub
commit b86e0f685f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 175 additions and 44 deletions

View file

@ -19,13 +19,13 @@
<div>
<NcListItem class="version"
:title="versionLabel"
:href="downloadURL"
:force-display-actions="true"
data-files-versions-version>
data-files-versions-version
@click="click">
<template #icon>
<div v-if="!(loadPreview || previewLoaded)" class="version__image" />
<img v-else-if="isCurrent || version.hasPreview"
:src="previewURL"
:src="version.previewUrl"
alt=""
decoding="async"
fetchpriority="low"
@ -46,7 +46,7 @@
</div>
</template>
<template #actions>
<NcActionButton v-if="enableLabeling"
<NcActionButton v-if="enableLabeling"
:close-after-click="true"
@click="openVersionLabelModal">
<template #icon>
@ -54,6 +54,14 @@
</template>
{{ version.label === '' ? t('files_versions', 'Name this version') : t('files_versions', 'Edit version name') }}
</NcActionButton>
<NcActionButton v-if="!isCurrent && canView && canCompare"
:close-after-click="true"
@click="compareVersion">
<template #icon>
<FileCompare :size="22" />
</template>
{{ t('files_versions', 'Compare to current version') }}
</NcActionButton>
<NcActionButton v-if="!isCurrent"
:close-after-click="true"
@click="restoreVersion">
@ -116,6 +124,7 @@
<script>
import BackupRestore from 'vue-material-design-icons/BackupRestore.vue'
import Download from 'vue-material-design-icons/Download.vue'
import FileCompare from 'vue-material-design-icons/FileCompare.vue'
import Pencil from 'vue-material-design-icons/Pencil.vue'
import Check from 'vue-material-design-icons/Check.vue'
import Delete from 'vue-material-design-icons/Delete.vue'
@ -124,7 +133,7 @@ import { NcActionButton, NcActionLink, NcListItem, NcModal, NcButton, NcTextFiel
import moment from '@nextcloud/moment'
import { translate } from '@nextcloud/l10n'
import { joinPaths } from '@nextcloud/paths'
import { generateUrl, getRootUrl } from '@nextcloud/router'
import { getRootUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
export default {
@ -138,6 +147,7 @@ export default {
NcTextField,
BackupRestore,
Download,
FileCompare,
Pencil,
Check,
Delete,
@ -184,6 +194,14 @@ export default {
type: Boolean,
default: false,
},
canView: {
type: Boolean,
default: false,
},
canCompare: {
type: Boolean,
default: false,
},
},
data() {
return {
@ -226,20 +244,6 @@ export default {
}
},
/**
* @return {string}
*/
previewURL() {
if (this.isCurrent) {
return generateUrl('/core/preview?fileId={fileId}&c={fileEtag}&x=250&y=250&forceIcon=0&a=0', {
fileId: this.fileInfo.id,
fileEtag: this.fileInfo.etag,
})
} else {
return this.version.preview
}
},
/** @return {string} */
formattedDate() {
return moment(this.version.mtime).format('LLL')
@ -253,7 +257,7 @@ export default {
/** @return {boolean} */
enableDeletion() {
return this.capabilities.files.version_deletion === true && this.fileInfo.mountType !== 'group'
}
},
},
methods: {
openVersionLabelModal() {
@ -276,6 +280,21 @@ export default {
deleteVersion() {
this.$emit('delete', this.version)
},
click() {
if (!this.canView) {
window.location = this.downloadURL
return
}
this.$emit('click', { version: this.version })
},
compareVersion() {
if (!this.canView) {
throw new Error('Cannot compare version of this file')
}
this.$emit('compare', { version: this.version })
},
},
}
</script>

View file

@ -29,6 +29,7 @@ export default `<?xml version="1.0"?>
<d:getcontentlength />
<d:getcontenttype />
<d:getlastmodified />
<d:getetag />
<nc:version-label />
<nc:has-preview />
</d:prop>

View file

@ -20,25 +20,33 @@
*/
import { getCurrentUser } from '@nextcloud/auth'
import { joinPaths } from '@nextcloud/paths'
import { generateRemoteUrl, generateUrl } from '@nextcloud/router'
import moment from '@nextcloud/moment'
import { encodeFilePath } from '../../../files/src/utils/fileUtils.js'
import client from '../utils/davClient.js'
import davRequest from '../utils/davRequest.js'
import logger from '../utils/logger.js'
import { joinPaths } from '@nextcloud/paths'
import { generateUrl } from '@nextcloud/router'
import moment from '@nextcloud/moment'
import path from 'path'
/**
* @typedef {object} Version
* @property {string} fileId - The id of the file associated to the version.
* @property {string} label - 'Current version' or ''
* @property {string} fileName - File name relative to the version DAV endpoint
* @property {string} mimeType - Empty for the current version, else the actual mime type of the version
* @property {string} filename - File name relative to the version DAV endpoint
* @property {string} basename - A base name generated from the mtime
* @property {string} mime - Empty for the current version, else the actual mime type of the version
* @property {string} etag - Empty for the current version, else the actual mime type of the version
* @property {string} size - Human readable size
* @property {string} type - 'file'
* @property {number} mtime - Version creation date as a timestamp
* @property {string} permissions - Only readable: 'R'
* @property {boolean} hasPreview - Whether the version has a preview
* @property {string} preview - Preview URL of the version
* @property {string} previewUrl - Preview URL of the version
* @property {string} url - Download URL of the version
* @property {string} source - The WebDAV endpoint of the ressource
* @property {string|null} fileVersion - The version id, null for the current version
*/
@ -75,7 +83,7 @@ export async function restoreVersion(version) {
logger.debug('Restoring version', { url: version.url })
await client.moveFile(
`/versions/${getCurrentUser()?.uid}/versions/${version.fileId}/${version.fileVersion}`,
`/versions/${getCurrentUser()?.uid}/restore/target`
`/versions/${getCurrentUser()?.uid}/restore/target`,
)
} catch (exception) {
logger.error('Could not restore version', { exception })
@ -91,20 +99,39 @@ export async function restoreVersion(version) {
* @return {Version}
*/
function formatVersion(version, fileInfo) {
const mtime = moment(version.lastmod).unix() * 1000
let previewUrl = ''
let filename = ''
if (mtime === fileInfo.mtime) { // Version is the current one
filename = path.join('files', getCurrentUser()?.uid ?? '', fileInfo.path, fileInfo.name)
previewUrl = generateUrl('/core/preview?fileId={fileId}&c={fileEtag}&x=250&y=250&forceIcon=0&a=0', {
fileId: fileInfo.id,
fileEtag: fileInfo.etag,
})
} else {
filename = version.filename
previewUrl = generateUrl('/apps/files_versions/preview?file={file}&version={fileVersion}', {
file: joinPaths(fileInfo.path, fileInfo.name),
fileVersion: version.basename,
})
}
return {
fileId: fileInfo.id,
label: version.props['version-label'],
fileName: version.filename,
mimeType: version.mime,
filename,
basename: moment(mtime).format('LLL'),
mime: version.mime,
etag: `${version.props.getetag}`,
size: version.size,
type: version.type,
mtime: moment(version.lastmod).unix() * 1000,
mtime,
permissions: 'R',
hasPreview: version.props['has-preview'] === 1,
preview: generateUrl('/apps/files_versions/preview?file={file}&version={fileVersion}', {
file: joinPaths(fileInfo.path, fileInfo.name),
fileVersion: version.basename,
}),
url: joinPaths('/remote.php/dav', version.filename),
previewUrl,
url: joinPaths('/remote.php/dav', filename),
source: generateRemoteUrl('dav') + encodeFilePath(filename),
fileVersion: version.basename,
}
}
@ -115,7 +142,7 @@ function formatVersion(version, fileInfo) {
*/
export async function setVersionLabel(version, newLabel) {
return await client.customRequest(
version.fileName,
version.filename,
{
method: 'PROPPATCH',
data: `<?xml version="1.0"?>
@ -129,7 +156,7 @@ export async function setVersionLabel(version, newLabel) {
</d:prop>
</d:set>
</d:propertyupdate>`,
}
},
)
}
@ -137,5 +164,5 @@ export async function setVersionLabel(version, newLabel) {
* @param {Version} version
*/
export async function deleteVersion(version) {
await client.deleteFile(version.fileName)
await client.deleteFile(version.filename)
}

View file

@ -19,11 +19,15 @@
<ul data-files-versions-versions-list>
<Version v-for="version in orderedVersions"
:key="version.mtime"
:can-view="canView"
:can-compare="canCompare"
:load-preview="isActive"
:version="version"
:file-info="fileInfo"
:is-current="version.mtime === fileInfo.mtime"
:is-first-version="version.mtime === initialVersionMtime"
@click="openVersion"
@compare="compareVersion"
@restore="handleRestore"
@label-update="handleLabelUpdate"
@delete="handleDelete" />
@ -32,6 +36,7 @@
<script>
import { showError, showSuccess } from '@nextcloud/dialogs'
import isMobile from '@nextcloud/vue/dist/Mixins/isMobile.js'
import { fetchVersions, deleteVersion, restoreVersion, setVersionLabel } from '../utils/versions.js'
import Version from '../components/Version.vue'
@ -40,6 +45,9 @@ export default {
components: {
Version,
},
mixins: [
isMobile,
],
data() {
return {
fileInfo: null,
@ -78,6 +86,37 @@ export default {
.map(version => version.mtime)
.reduce((a, b) => Math.min(a, b))
},
viewerFileInfo() {
// We need to remap bitmask to dav permissions as the file info we have is converted through client.js
let davPermissions = ''
if (this.fileInfo.permissions & 1) {
davPermissions += 'R'
}
if (this.fileInfo.permissions & 2) {
davPermissions += 'W'
}
if (this.fileInfo.permissions & 8) {
davPermissions += 'D'
}
return {
...this.fileInfo,
mime: this.fileInfo.mimetype,
basename: this.fileInfo.name,
filename: this.fileInfo.path + this.fileInfo.name,
permissions: davPermissions,
fileid: this.fileInfo.id,
}
},
/** @return {boolean} */
canView() {
return window.OCA.Viewer?.mimetypesCompare?.includes(this.fileInfo.mimetype)
},
canCompare() {
return !this.isMobile
},
},
methods: {
/**
@ -182,6 +221,29 @@ export default {
resetState() {
this.$set(this, 'versions', [])
},
openVersion({ version }) {
// Open current file view instead of read only
if (version.mtime === this.fileInfo.mtime) {
OCA.Viewer.open({ fileInfo: this.viewerFileInfo })
return
}
// Versions previews are too small for our use case, so we override hasPreview and previewUrl
// which makes the viewer render the original file.
const versions = this.versions.map(version => ({ ...version, hasPreview: false, previewUrl: undefined }))
OCA.Viewer.open({
fileInfo: versions.find(v => v.source === version.source),
enableSidebar: false,
})
},
compareVersion({ version }) {
const versions = this.versions.map(version => ({ ...version, hasPreview: false, previewUrl: undefined }))
OCA.Viewer.compare(this.viewerFileInfo, versions.find(v => v.source === version.source))
},
},
}
</script>

4
dist/core-common.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -37,3 +37,25 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @copyright Copyright (c) 2021 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

File diff suppressed because one or more lines are too long