<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"
</div>
</template>
<template #actions>
- <NcActionButton v-if="enableLabeling"
+ <NcActionButton v-if="enableLabeling"
:close-after-click="true"
@click="openVersionLabelModal">
<template #icon>
</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">
<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'
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 {
NcTextField,
BackupRestore,
Download,
+ FileCompare,
Pencil,
Check,
Delete,
type: Boolean,
default: false,
},
+ canView: {
+ type: Boolean,
+ default: false,
+ },
+ canCompare: {
+ type: Boolean,
+ default: false,
+ },
},
data() {
return {
}
},
- /**
- * @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')
/** @return {boolean} */
enableDeletion() {
return this.capabilities.files.version_deletion === true && this.fileInfo.mountType !== 'group'
- }
+ },
},
methods: {
openVersionLabelModal() {
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>
<d:getcontentlength />
<d:getcontenttype />
<d:getlastmodified />
+ <d:getetag />
<nc:version-label />
<nc:has-preview />
</d:prop>
*/
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
*/
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 })
* @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,
}
}
*/
export async function setVersionLabel(version, newLabel) {
return await client.customRequest(
- version.fileName,
+ version.filename,
{
method: 'PROPPATCH',
data: `<?xml version="1.0"?>
</d:prop>
</d:set>
</d:propertyupdate>`,
- }
+ },
)
}
* @param {Version} version
*/
export async function deleteVersion(version) {
- await client.deleteFile(version.fileName)
+ await client.deleteFile(version.filename)
}
<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" />
<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'
components: {
Version,
},
+ mixins: [
+ isMobile,
+ ],
data() {
return {
fileInfo: null,
.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: {
/**
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>