]> source.dussan.org Git - nextcloud-server.git/commitdiff
Move modal outside of the Version component.
authorLouis Chemineau <louis@chmn.me>
Thu, 25 Jan 2024 09:25:16 +0000 (10:25 +0100)
committerLouis Chemineau <louis@chmn.me>
Fri, 26 Jan 2024 14:41:50 +0000 (15:41 +0100)
This is for accessibility, to have the NcListItem (<li>) as a direct child of the <ul>

Signed-off-by: Louis Chemineau <louis@chmn.me>
apps/files_versions/src/components/Version.vue
apps/files_versions/src/components/VersionLabelForm.vue [new file with mode: 0644]
apps/files_versions/src/utils/versions.js [deleted file]
apps/files_versions/src/utils/versions.ts [new file with mode: 0644]
apps/files_versions/src/views/VersionTab.vue

index 5f4e7b447ea47800218542ffa3449e1d64569389..a03e71bc5dfdd0c8c22f8a49fb6d24926f61e03b 100644 (file)
  - along with this program. If not, see <http://www.gnu.org/licenses/>.
  -->
 <template>
-       <div>
-               <NcListItem class="version"
-                       :name="versionLabel"
-                       :force-display-actions="true"
-                       data-files-versions-version
-                       @click="click">
-                       <template #icon>
-                               <div v-if="!(loadPreview || previewLoaded)" class="version__image" />
-                               <img v-else-if="(isCurrent || version.hasPreview) && !previewErrored"
-                                       :src="version.previewUrl"
-                                       alt=""
-                                       decoding="async"
-                                       fetchpriority="low"
-                                       loading="lazy"
-                                       class="version__image"
-                                       @load="previewLoaded = true"
-                                       @error="previewErrored = true">
-                               <div v-else
-                                       class="version__image">
-                                       <ImageOffOutline :size="20" />
-                               </div>
-                       </template>
-                       <template #subname>
-                               <div class="version__info">
-                                       <span :title="formattedDate">{{ version.mtime | humanDateFromNow }}</span>
-                                       <!-- Separate dot to improve alignement -->
-                                       <span class="version__info__size">•</span>
-                                       <span class="version__info__size">{{ version.size | humanReadableSize }}</span>
-                               </div>
-                       </template>
-                       <template #actions>
-                               <NcActionButton v-if="enableLabeling"
-                                       :close-after-click="true"
-                                       @click="openVersionLabelModal">
-                                       <template #icon>
-                                               <Pencil :size="22" />
-                                       </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">
-                                       <template #icon>
-                                               <BackupRestore :size="22" />
-                                       </template>
-                                       {{ t('files_versions', 'Restore version') }}
-                               </NcActionButton>
-                               <NcActionLink :href="downloadURL"
-                                       :close-after-click="true"
-                                       :download="downloadURL">
-                                       <template #icon>
-                                               <Download :size="22" />
-                                       </template>
-                                       {{ t('files_versions', 'Download version') }}
-                               </NcActionLink>
-                               <NcActionButton v-if="!isCurrent && enableDeletion"
-                                       :close-after-click="true"
-                                       @click="deleteVersion">
-                                       <template #icon>
-                                               <Delete :size="22" />
-                                       </template>
-                                       {{ t('files_versions', 'Delete version') }}
-                               </NcActionButton>
-                       </template>
-               </NcListItem>
-               <NcModal v-if="showVersionLabelForm"
-                       :title="t('files_versions', 'Name this version')"
-                       @close="showVersionLabelForm = false">
-                       <form class="version-label-modal"
-                               @submit.prevent="setVersionLabel(formVersionLabelValue)">
-                               <label>
-                                       <div class="version-label-modal__title">{{ t('files_versions', 'Version name') }}</div>
-                                       <NcTextField ref="labelInput"
-                                               :value.sync="formVersionLabelValue"
-                                               :placeholder="t('files_versions', 'Version name')"
-                                               :label-outside="true" />
-                               </label>
-
-                               <div class="version-label-modal__info">
-                                       {{ t('files_versions', 'Named versions are persisted, and excluded from automatic cleanups when your storage quota is full.') }}
-                               </div>
-
-                               <div class="version-label-modal__actions">
-                                       <NcButton :disabled="formVersionLabelValue.trim().length === 0" @click="setVersionLabel('')">
-                                               {{ t('files_versions', 'Remove version name') }}
-                                       </NcButton>
-                                       <NcButton type="primary" native-type="submit">
-                                               <template #icon>
-                                                       <Check />
-                                               </template>
-                                               {{ t('files_versions', 'Save version name') }}
-                                       </NcButton>
-                               </div>
-                       </form>
-               </NcModal>
-       </div>
+       <NcListItem class="version"
+               :name="versionLabel"
+               :force-display-actions="true"
+               data-files-versions-version
+               @click="click">
+               <template #icon>
+                       <div v-if="!(loadPreview || previewLoaded)" class="version__image" />
+                       <img v-else-if="(isCurrent || version.hasPreview) && !previewErrored"
+                               :src="version.previewUrl"
+                               alt=""
+                               decoding="async"
+                               fetchpriority="low"
+                               loading="lazy"
+                               class="version__image"
+                               @load="previewLoaded = true"
+                               @error="previewErrored = true">
+                       <div v-else
+                               class="version__image">
+                               <ImageOffOutline :size="20" />
+                       </div>
+               </template>
+               <template #subname>
+                       <div class="version__info">
+                               <span :title="formattedDate">{{ version.mtime | humanDateFromNow }}</span>
+                               <!-- Separate dot to improve alignement -->
+                               <span class="version__info__size">•</span>
+                               <span class="version__info__size">{{ version.size | humanReadableSize }}</span>
+                       </div>
+               </template>
+               <template #actions>
+                       <NcActionButton v-if="enableLabeling"
+                               :close-after-click="true"
+                               @click="labelUpdate">
+                               <template #icon>
+                                       <Pencil :size="22" />
+                               </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">
+                               <template #icon>
+                                       <BackupRestore :size="22" />
+                               </template>
+                               {{ t('files_versions', 'Restore version') }}
+                       </NcActionButton>
+                       <NcActionLink :href="downloadURL"
+                               :close-after-click="true"
+                               :download="downloadURL">
+                               <template #icon>
+                                       <Download :size="22" />
+                               </template>
+                               {{ t('files_versions', 'Download version') }}
+                       </NcActionLink>
+                       <NcActionButton v-if="!isCurrent && enableDeletion"
+                               :close-after-click="true"
+                               @click="deleteVersion">
+                               <template #icon>
+                                       <Delete :size="22" />
+                               </template>
+                               {{ t('files_versions', 'Delete version') }}
+                       </NcActionButton>
+               </template>
+       </NcListItem>
 </template>
 
 <script>
@@ -127,15 +95,11 @@ 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 ImageOffOutline from 'vue-material-design-icons/ImageOffOutline.vue'
 import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
 import NcActionLink from '@nextcloud/vue/dist/Components/NcActionLink.js'
 import NcListItem from '@nextcloud/vue/dist/Components/NcListItem.js'
-import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
-import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
-import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
 import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip.js'
 import moment from '@nextcloud/moment'
 import { translate as t } from '@nextcloud/l10n'
@@ -149,14 +113,10 @@ export default {
                NcActionLink,
                NcActionButton,
                NcListItem,
-               NcModal,
-               NcButton,
-               NcTextField,
                BackupRestore,
                Download,
                FileCompare,
                Pencil,
-               Check,
                Delete,
                ImageOffOutline,
        },
@@ -180,7 +140,7 @@ export default {
                },
        },
        props: {
-               /** @type {Vue.PropOptions<import('../utils/versions.js').Version>} */
+               /** @type {Vue.PropOptions<import('../utils/versions.ts').Version>} */
                version: {
                        type: Object,
                        required: true,
@@ -214,8 +174,6 @@ export default {
                return {
                        previewLoaded: false,
                        previewErrored: false,
-                       showVersionLabelForm: false,
-                       formVersionLabelValue: this.version.label,
                        capabilities: loadState('core', 'capabilities', { files: { version_labeling: false, version_deletion: false } }),
                }
        },
@@ -268,23 +226,14 @@ export default {
                },
        },
        methods: {
-               openVersionLabelModal() {
-                       this.showVersionLabelForm = true
-                       this.$nextTick(() => {
-                               this.$refs.labelInput.$el.getElementsByTagName('input')[0].focus()
-                       })
+               labelUpdate() {
+                       this.$emit('label-update-request')
                },
 
                restoreVersion() {
                        this.$emit('restore', this.version)
                },
 
-               setVersionLabel(label) {
-                       this.formVersionLabelValue = label
-                       this.showVersionLabelForm = false
-                       this.$emit('label-update', this.version, label)
-               },
-
                deleteVersion() {
                        this.$emit('delete', this.version)
                },
@@ -337,28 +286,4 @@ export default {
                color: var(--color-text-light);
        }
 }
-
-.version-label-modal {
-       display: flex;
-       justify-content: space-between;
-       flex-direction: column;
-       height: 250px;
-       padding: 16px;
-
-       &__title {
-               margin-bottom: 12px;
-               font-weight: 600;
-       }
-
-       &__info {
-               margin-top: 12px;
-               color: var(--color-text-maxcontrast);
-       }
-
-       &__actions {
-               display: flex;
-               justify-content: space-between;
-               margin-top: 64px;
-       }
-}
 </style>
diff --git a/apps/files_versions/src/components/VersionLabelForm.vue b/apps/files_versions/src/components/VersionLabelForm.vue
new file mode 100644 (file)
index 0000000..5b8251b
--- /dev/null
@@ -0,0 +1,115 @@
+<!--
+ - @copyright Copyright (c) 2024 Louis Chemineau <louis@chmn.me>
+ -
+ - @author Louis Chemineau <louis@chmn.me>
+ -
+ - @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/>.
+ -->
+<template>
+       <form class="version-label-modal"
+               @submit.prevent="setVersionLabel(innerVersionLabel)">
+               <label>
+                       <div class="version-label-modal__title">{{ t('files_versions', 'Version name') }}</div>
+                       <NcTextField ref="labelInput"
+                               :value.sync="innerVersionLabel"
+                               :placeholder="t('files_versions', 'Version name')"
+                               :label-outside="true" />
+               </label>
+
+               <div class="version-label-modal__info">
+                       {{ t('files_versions', 'Named versions are persisted, and excluded from automatic cleanups when your storage quota is full.') }}
+               </div>
+
+               <div class="version-label-modal__actions">
+                       <NcButton :disabled="innerVersionLabel.trim().length === 0" @click="setVersionLabel('')">
+                               {{ t('files_versions', 'Remove version name') }}
+                       </NcButton>
+                       <NcButton type="primary" native-type="submit">
+                               <template #icon>
+                                       <Check />
+                               </template>
+                               {{ t('files_versions', 'Save version name') }}
+                       </NcButton>
+               </div>
+       </form>
+</template>
+
+<script lang="ts">
+import Check from 'vue-material-design-icons/Check.vue'
+import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
+import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
+import { translate } from '@nextcloud/l10n'
+
+import { defineComponent } from 'vue'
+
+export default defineComponent({
+       name: 'VersionLabelForm',
+       components: {
+               NcButton,
+               NcTextField,
+               Check,
+       },
+       props: {
+               versionLabel: {
+                       type: String,
+                       default: '',
+               },
+       },
+       data() {
+               return {
+                       innerVersionLabel: this.versionLabel,
+               }
+       },
+       mounted() {
+               this.$nextTick(() => {
+                       (this.$refs.labelInput as Vue).$el.getElementsByTagName('input')[0].focus()
+               })
+       },
+       methods: {
+               setVersionLabel(label: string) {
+                       this.$emit('label-update', label)
+               },
+
+               t: translate,
+       },
+})
+</script>
+
+<style scoped lang="scss">
+.version-label-modal {
+       display: flex;
+       justify-content: space-between;
+       flex-direction: column;
+       height: 250px;
+       padding: 16px;
+
+       &__title {
+               margin-bottom: 12px;
+               font-weight: 600;
+       }
+
+       &__info {
+               margin-top: 12px;
+               color: var(--color-text-maxcontrast);
+       }
+
+       &__actions {
+               display: flex;
+               justify-content: space-between;
+               margin-top: 64px;
+       }
+}
+</style>
diff --git a/apps/files_versions/src/utils/versions.js b/apps/files_versions/src/utils/versions.js
deleted file mode 100644 (file)
index 98df139..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-/**
- * @copyright 2022 Louis Chemineau <mlouis@chmn.me>
- *
- * @author Louis Chemineau <mlouis@chmn.me>
- *
- * @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/>.
- */
-
-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.ts'
-
-import client from '../utils/davClient.js'
-import davRequest from '../utils/davRequest.js'
-import logger from '../utils/logger.js'
-
-/**
- * @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} 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} 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
- */
-
-/**
- * @param fileInfo
- * @return {Promise<Version[]>}
- */
-export async function fetchVersions(fileInfo) {
-       const path = `/versions/${getCurrentUser()?.uid}/versions/${fileInfo.id}`
-
-       try {
-               /** @type {import('webdav').ResponseDataDetailed<import('webdav').FileStat[]>} */
-               const response = await client.getDirectoryContents(path, {
-                       data: davRequest,
-                       details: true,
-               })
-               return response.data
-                       // Filter out root
-                       .filter(({ mime }) => mime !== '')
-                       .map(version => formatVersion(version, fileInfo))
-       } catch (exception) {
-               logger.error('Could not fetch version', { exception })
-               throw exception
-       }
-}
-
-/**
- * Restore the given version
- *
- * @param {Version} version
- */
-export async function restoreVersion(version) {
-       try {
-               logger.debug('Restoring version', { url: version.url })
-               await client.moveFile(
-                       `/versions/${getCurrentUser()?.uid}/versions/${version.fileId}/${version.fileVersion}`,
-                       `/versions/${getCurrentUser()?.uid}/restore/target`,
-               )
-       } catch (exception) {
-               logger.error('Could not restore version', { exception })
-               throw exception
-       }
-}
-
-/**
- * Format version
- *
- * @param {object} version - raw version received from the versions DAV endpoint
- * @param {object} fileInfo - file properties received from the files DAV endpoint
- * @return {Version}
- */
-function formatVersion(version, fileInfo) {
-       const mtime = moment(version.lastmod).unix() * 1000
-       let previewUrl = ''
-
-       if (mtime === fileInfo.mtime) { // Version is the current one
-               previewUrl = generateUrl('/core/preview?fileId={fileId}&c={fileEtag}&x=250&y=250&forceIcon=0&a=0', {
-                       fileId: fileInfo.id,
-                       fileEtag: fileInfo.etag,
-               })
-       } else {
-               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,
-               basename: moment(mtime).format('LLL'),
-               mime: version.mime,
-               etag: `${version.props.getetag}`,
-               size: version.size,
-               type: version.type,
-               mtime,
-               permissions: 'R',
-               hasPreview: version.props['has-preview'] === 1,
-               previewUrl,
-               url: joinPaths('/remote.php/dav', version.filename),
-               source: generateRemoteUrl('dav') + encodeFilePath(version.filename),
-               fileVersion: version.basename,
-       }
-}
-
-/**
- * @param {Version} version
- * @param {string} newLabel
- */
-export async function setVersionLabel(version, newLabel) {
-       return await client.customRequest(
-               version.filename,
-               {
-                       method: 'PROPPATCH',
-                       data: `<?xml version="1.0"?>
-                                       <d:propertyupdate xmlns:d="DAV:"
-                                               xmlns:oc="http://owncloud.org/ns"
-                                               xmlns:nc="http://nextcloud.org/ns"
-                                               xmlns:ocs="http://open-collaboration-services.org/ns">
-                                       <d:set>
-                                               <d:prop>
-                                                       <nc:version-label>${newLabel}</nc:version-label>
-                                               </d:prop>
-                                       </d:set>
-                                       </d:propertyupdate>`,
-               },
-       )
-}
-
-/**
- * @param {Version} version
- */
-export async function deleteVersion(version) {
-       await client.deleteFile(version.filename)
-}
diff --git a/apps/files_versions/src/utils/versions.ts b/apps/files_versions/src/utils/versions.ts
new file mode 100644 (file)
index 0000000..b33f75a
--- /dev/null
@@ -0,0 +1,150 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+/* eslint-disable jsdoc/require-param */
+/* eslint-disable jsdoc/require-jsdoc */
+/**
+ * @copyright 2022 Louis Chemineau <mlouis@chmn.me>
+ *
+ * @author Louis Chemineau <mlouis@chmn.me>
+ *
+ * @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/>.
+ */
+
+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.ts'
+
+import client from '../utils/davClient.js'
+import davRequest from '../utils/davRequest.js'
+import logger from '../utils/logger.js'
+import type { FileStat, ResponseDataDetailed } from 'webdav'
+
+export interface Version {
+       fileId: string, // The id of the file associated to the version.
+       label: string, // 'Current version' or ''
+       filename: string, // File name relative to the version DAV endpoint
+       basename: string, // A base name generated from the mtime
+       mime: string, // Empty for the current version, else the actual mime type of the version
+       etag: string, // Empty for the current version, else the actual mime type of the version
+       size: string, // Human readable size
+       type: string, // 'file'
+       mtime: number, // Version creation date as a timestamp
+       permissions: string, // Only readable: 'R'
+       hasPreview: boolean, // Whether the version has a preview
+       previewUrl: string, // Preview URL of the version
+       url: string, // Download URL of the version
+       source: string, // The WebDAV endpoint of the ressource
+       fileVersion: string|null, // The version id, null for the current version
+}
+
+export async function fetchVersions(fileInfo: any): Promise<Version[]> {
+       const path = `/versions/${getCurrentUser()?.uid}/versions/${fileInfo.id}`
+
+       try {
+               const response = await client.getDirectoryContents(path, {
+                       data: davRequest,
+                       details: true,
+               }) as ResponseDataDetailed<FileStat[]>
+
+               return response.data
+                       // Filter out root
+                       .filter(({ mime }) => mime !== '')
+                       .map(version => formatVersion(version, fileInfo))
+       } catch (exception) {
+               logger.error('Could not fetch version', { exception })
+               throw exception
+       }
+}
+
+/**
+ * Restore the given version
+ */
+export async function restoreVersion(version: Version) {
+       try {
+               logger.debug('Restoring version', { url: version.url })
+               await client.moveFile(
+                       `/versions/${getCurrentUser()?.uid}/versions/${version.fileId}/${version.fileVersion}`,
+                       `/versions/${getCurrentUser()?.uid}/restore/target`,
+               )
+       } catch (exception) {
+               logger.error('Could not restore version', { exception })
+               throw exception
+       }
+}
+
+/**
+ * Format version
+ */
+function formatVersion(version: any, fileInfo: any): Version {
+       const mtime = moment(version.lastmod).unix() * 1000
+       let previewUrl = ''
+
+       if (mtime === fileInfo.mtime) { // Version is the current one
+               previewUrl = generateUrl('/core/preview?fileId={fileId}&c={fileEtag}&x=250&y=250&forceIcon=0&a=0', {
+                       fileId: fileInfo.id,
+                       fileEtag: fileInfo.etag,
+               })
+       } else {
+               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,
+               basename: moment(mtime).format('LLL'),
+               mime: version.mime,
+               etag: `${version.props.getetag}`,
+               size: version.size,
+               type: version.type,
+               mtime,
+               permissions: 'R',
+               hasPreview: version.props['has-preview'] === 1,
+               previewUrl,
+               url: joinPaths('/remote.php/dav', version.filename),
+               source: generateRemoteUrl('dav') + encodeFilePath(version.filename),
+               fileVersion: version.basename,
+       }
+}
+
+export async function setVersionLabel(version: Version, newLabel: string) {
+       return await client.customRequest(
+               version.filename,
+               {
+                       method: 'PROPPATCH',
+                       data: `<?xml version="1.0"?>
+                                       <d:propertyupdate xmlns:d="DAV:"
+                                               xmlns:oc="http://owncloud.org/ns"
+                                               xmlns:nc="http://nextcloud.org/ns"
+                                               xmlns:ocs="http://open-collaboration-services.org/ns">
+                                       <d:set>
+                                               <d:prop>
+                                                       <nc:version-label>${newLabel}</nc:version-label>
+                                               </d:prop>
+                                       </d:set>
+                                       </d:propertyupdate>`,
+               },
+       )
+}
+
+export async function deleteVersion(version: Version) {
+       await client.deleteFile(version.filename)
+}
index c039e4a6c3a0473e01a1cbafc4225d329f3dae93..745b9d0f58e51bad9b29f4168e0bf813476f5929 100644 (file)
  - along with this program. If not, see <http://www.gnu.org/licenses/>.
  -->
 <template>
-       <VirtualScrolling :sections="sections"
-               :header-height="0">
-               <template slot-scope="{visibleSections}">
-                       <ul data-files-versions-versions-list>
-                               <template v-if="visibleSections.length === 1">
-                                       <Version v-for="(row) of visibleSections[0].rows"
-                                               :key="row.items[0].mtime"
-                                               :can-view="canView"
-                                               :can-compare="canCompare"
-                                               :load-preview="isActive"
-                                               :version="row.items[0]"
-                                               :file-info="fileInfo"
-                                               :is-current="row.items[0].mtime === fileInfo.mtime"
-                                               :is-first-version="row.items[0].mtime === initialVersionMtime"
-                                               @click="openVersion"
-                                               @compare="compareVersion"
-                                               @restore="handleRestore"
-                                               @label-update="handleLabelUpdate"
-                                               @delete="handleDelete" />
-                               </template>
-                       </ul>
-               </template>
-               <NcLoadingIcon v-if="loading" slot="loader" class="files-list-viewer__loader" />
-       </VirtualScrolling>
+       <div class="versions-tab__container">
+               <VirtualScrolling :sections="sections"
+                       :header-height="0">
+                       <template slot-scope="{visibleSections}">
+                               <ul data-files-versions-versions-list>
+                                       <template v-if="visibleSections.length === 1">
+                                               <Version v-for="(row) of visibleSections[0].rows"
+                                                       :key="row.items[0].mtime"
+                                                       :can-view="canView"
+                                                       :can-compare="canCompare"
+                                                       :load-preview="isActive"
+                                                       :version="row.items[0]"
+                                                       :file-info="fileInfo"
+                                                       :is-current="row.items[0].mtime === fileInfo.mtime"
+                                                       :is-first-version="row.items[0].mtime === initialVersionMtime"
+                                                       @click="openVersion"
+                                                       @compare="compareVersion"
+                                                       @restore="handleRestore"
+                                                       @label-update-request="handleLabelUpdateRequest(row.items[0])"
+                                                       @delete="handleDelete" />
+                                       </template>
+                               </ul>
+                       </template>
+                       <NcLoadingIcon v-if="loading" slot="loader" class="files-list-viewer__loader" />
+               </VirtualScrolling>
+               <NcModal v-if="showVersionLabelForm"
+                       :title="t('files_versions', 'Name this version')"
+                       @close="showVersionLabelForm = false">
+                       <VersionLabelForm :version-label="editedVersion.label" @label-update="handleLabelUpdate" />
+               </NcModal>
+       </div>
 </template>
 
 <script>
@@ -49,18 +56,22 @@ import { showError, showSuccess } from '@nextcloud/dialogs'
 import isMobile from '@nextcloud/vue/dist/Mixins/isMobile.js'
 import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
 import { getCurrentUser } from '@nextcloud/auth'
-import { NcLoadingIcon } from '@nextcloud/vue'
+import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
+import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
 
-import { fetchVersions, deleteVersion, restoreVersion, setVersionLabel } from '../utils/versions.js'
+import { fetchVersions, deleteVersion, restoreVersion, setVersionLabel } from '../utils/versions.ts'
 import Version from '../components/Version.vue'
 import VirtualScrolling from '../components/VirtualScrolling.vue'
+import VersionLabelForm from '../components/VersionLabelForm.vue'
 
 export default {
        name: 'VersionTab',
        components: {
                Version,
                VirtualScrolling,
+               VersionLabelForm,
                NcLoadingIcon,
+               NcModal,
        },
        mixins: [
                isMobile,
@@ -69,17 +80,12 @@ export default {
                return {
                        fileInfo: null,
                        isActive: false,
-                       /** @type {import('../utils/versions.js').Version[]} */
+                       /** @type {import('../utils/versions.ts').Version[]} */
                        versions: [],
                        loading: false,
+                       showVersionLabelForm: false,
                }
        },
-       mounted() {
-               subscribe('files_versions:restore:restored', this.fetchVersions)
-       },
-       beforeUnmount() {
-               unsubscribe('files_versions:restore:restored', this.fetchVersions)
-       },
        computed: {
                sections() {
                        const rows = this.orderedVersions.map(version => ({ key: version.mtime, height: 68, sectionKey: 'versions', items: [version] }))
@@ -90,7 +96,7 @@ export default {
                 * Order versions by mtime.
                 * Put the current version at the top.
                 *
-                * @return {import('../utils/versions.js').Version[]}
+                * @return {import('../utils/versions.ts').Version[]}
                 */
                orderedVersions() {
                        return [...this.versions].sort((a, b) => {
@@ -146,6 +152,12 @@ export default {
                        return !this.isMobile
                },
        },
+       mounted() {
+               subscribe('files_versions:restore:restored', this.fetchVersions)
+       },
+       beforeUnmount() {
+               unsubscribe('files_versions:restore:restored', this.fetchVersions)
+       },
        methods: {
                /**
                 * Update current fileInfo and fetch new data
@@ -180,7 +192,7 @@ export default {
                /**
                 * Handle restored event from Version.vue
                 *
-                * @param {import('../utils/versions.js').Version} version
+                * @param {import('../utils/versions.ts').Version} version
                 */
                async handleRestore(version) {
                        // Update local copy of fileInfo as rendering depends on it.
@@ -220,26 +232,36 @@ export default {
 
                /**
                 * Handle label-updated event from Version.vue
-                *
-                * @param {import('../utils/versions.js').Version} version
-                * @param {string} newName
+                * @param {import('../utils/versions.ts').Version} version
+                */
+               handleLabelUpdateRequest(version) {
+                       this.showVersionLabelForm = true
+                       this.editedVersion = version
+               },
+
+               /**
+                * Handle label-updated event from Version.vue
+                * @param {string} newLabel
                 */
-               async handleLabelUpdate(version, newName) {
-                       const oldLabel = version.label
-                       version.label = newName
+               async handleLabelUpdate(newLabel) {
+                       const oldLabel = this.editedVersion.label
+                       this.editedVersion.label = newLabel
+                       this.showVersionLabelForm = false
 
                        try {
-                               await setVersionLabel(version, newName)
+                               await setVersionLabel(this.editedVersion, newLabel)
+                               this.editedVersion = null
                        } catch (exception) {
-                               version.label = oldLabel
-                               showError(t('files_versions', 'Could not set version name'))
+                               this.editedVersion.label = oldLabel
+                               showError(this.t('files_versions', 'Could not set version label'))
+                               logger.error('Could not set version label', { exception })
                        }
                },
 
                /**
                 * Handle deleted event from Version.vue
                 *
-                * @param {import('../utils/versions.js').Version} version
+                * @param {import('../utils/versions.ts').Version} version
                 * @param {string} newName
                 */
                async handleDelete(version) {
@@ -292,3 +314,8 @@ export default {
        },
 }
 </script>
+<style lang="scss">
+.versions-tab__container {
+       height: 100%;
+}
+</style>