diff options
Diffstat (limited to 'apps/files_versions/src')
-rw-r--r-- | apps/files_versions/src/components/Version.vue | 166 | ||||
-rw-r--r-- | apps/files_versions/src/components/VersionLabelDialog.vue | 123 | ||||
-rw-r--r-- | apps/files_versions/src/components/VersionLabelForm.vue | 115 | ||||
-rw-r--r-- | apps/files_versions/src/components/VirtualScrolling.vue | 23 | ||||
-rw-r--r-- | apps/files_versions/src/css/versions.css | 17 | ||||
-rw-r--r-- | apps/files_versions/src/files_versions_tab.js | 22 | ||||
-rw-r--r-- | apps/files_versions/src/utils/davClient.js | 34 | ||||
-rw-r--r-- | apps/files_versions/src/utils/davRequest.js | 21 | ||||
-rw-r--r-- | apps/files_versions/src/utils/logger.js | 20 | ||||
-rw-r--r-- | apps/files_versions/src/utils/versions.ts | 29 | ||||
-rw-r--r-- | apps/files_versions/src/views/VersionTab.vue | 108 |
11 files changed, 300 insertions, 378 deletions
diff --git a/apps/files_versions/src/components/Version.vue b/apps/files_versions/src/components/Version.vue index 1afe28250c6..dc36e4134f9 100644 --- a/apps/files_versions/src/components/Version.vue +++ b/apps/files_versions/src/components/Version.vue @@ -1,29 +1,17 @@ <!-- - - @copyright 2022 Carl Schwan <carl@carlschwan.eu> - - @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/>. - --> + - SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> <template> <NcListItem class="version" :force-display-actions="true" + :actions-aria-label="t('files_versions', 'Actions for version from {versionHumanExplicitDate}', { versionHumanExplicitDate })" :data-files-versions-version="version.fileVersion" @click="click"> <!-- Icon --> <template #icon> <div v-if="!(loadPreview || previewLoaded)" class="version__image" /> - <img v-else-if="(isCurrent || version.hasPreview) && !previewErrored" + <img v-else-if="version.previewUrl && !previewErrored" :src="version.previewUrl" alt="" decoding="async" @@ -41,16 +29,26 @@ <!-- author --> <template #name> <div class="version__info"> - <div v-if="versionLabel" class="version__info__label">{{ versionLabel }}</div> - <div v-if="versionAuthor" class="version__info"> - <div v-if="versionLabel">•</div> + <div v-if="versionLabel" + class="version__info__label" + data-cy-files-version-label + :title="versionLabel"> + {{ versionLabel }} + </div> + <div v-if="versionAuthor" + class="version__info" + data-cy-files-version-author-name> + <span v-if="versionLabel">•</span> <NcAvatar class="avatar" :user="version.author" - :size="16" - :disable-menu="true" - :disable-tooltip="true" + :size="20" + disable-menu + disable-tooltip :show-user-status="false" /> - <div>{{ versionAuthor }}</div> + <div class="version__info__author_name" + :title="versionAuthor"> + {{ versionAuthor }} + </div> </div> </div> </template> @@ -58,10 +56,12 @@ <!-- Version file size as subline --> <template #subname> <div class="version__info version__info__subline"> - <span :title="formattedDate">{{ version.mtime | humanDateFromNow }}</span> - <!-- Separate dot to improve alignement --> + <NcDateTime class="version__info__date" + relative-time="short" + :timestamp="version.mtime" /> + <!-- Separate dot to improve alignment --> <span>•</span> - <span>{{ version.size | humanReadableSize }}</span> + <span>{{ humanReadableSize }}</span> </div> </template> @@ -116,31 +116,35 @@ </template> </NcListItem> </template> - <script lang="ts"> +import type { PropType } from 'vue' import type { Version } from '../utils/versions' +import { getCurrentUser } from '@nextcloud/auth' +import { Permission, formatFileSize } from '@nextcloud/files' +import { loadState } from '@nextcloud/initial-state' +import { t } from '@nextcloud/l10n' +import { joinPaths } from '@nextcloud/paths' +import { getRootUrl, generateUrl } from '@nextcloud/router' +import { defineComponent } from 'vue' + +import axios from '@nextcloud/axios' +import moment from '@nextcloud/moment' +import logger from '../utils/logger' + import BackupRestore from 'vue-material-design-icons/BackupRestore.vue' import Delete from 'vue-material-design-icons/Delete.vue' import Download from 'vue-material-design-icons/Download.vue' import FileCompare from 'vue-material-design-icons/FileCompare.vue' import ImageOffOutline from 'vue-material-design-icons/ImageOffOutline.vue' -import Pencil from 'vue-material-design-icons/Pencil.vue' +import Pencil from 'vue-material-design-icons/PencilOutline.vue' -import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' -import NcActionLink from '@nextcloud/vue/dist/Components/NcActionLink.js' -import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js' -import NcListItem from '@nextcloud/vue/dist/Components/NcListItem.js' -import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip.js' - -import { defineComponent, type PropType } from 'vue' -import axios from '@nextcloud/axios' -import { getRootUrl, generateOcsUrl } from '@nextcloud/router' -import { joinPaths } from '@nextcloud/paths' -import { loadState } from '@nextcloud/initial-state' -import { Permission, formatFileSize } from '@nextcloud/files' -import { translate as t } from '@nextcloud/l10n' -import moment from '@nextcloud/moment' +import NcActionButton from '@nextcloud/vue/components/NcActionButton' +import NcActionLink from '@nextcloud/vue/components/NcActionLink' +import NcAvatar from '@nextcloud/vue/components/NcAvatar' +import NcDateTime from '@nextcloud/vue/components/NcDateTime' +import NcListItem from '@nextcloud/vue/components/NcListItem' +import Tooltip from '@nextcloud/vue/directives/Tooltip' const hasPermission = (permissions: number, permission: number): boolean => (permissions & permission) !== 0 @@ -151,6 +155,7 @@ export default defineComponent({ NcActionLink, NcActionButton, NcAvatar, + NcDateTime, NcListItem, BackupRestore, Download, @@ -164,20 +169,6 @@ export default defineComponent({ tooltip: Tooltip, }, - created() { - this.fetchDisplayName() - }, - - filters: { - humanReadableSize(bytes: number): string { - return formatFileSize(bytes) - }, - - humanDateFromNow(timestamp: number): string { - return moment(timestamp).fromNow() - }, - }, - props: { version: { type: Object as PropType<Version>, @@ -216,11 +207,15 @@ export default defineComponent({ previewLoaded: false, previewErrored: false, capabilities: loadState('core', 'capabilities', { files: { version_labeling: false, version_deletion: false } }), - versionAuthor: '', + versionAuthor: '' as string | null, } }, computed: { + humanReadableSize() { + return formatFileSize(this.version.size) + }, + versionLabel(): string { const label = this.version.label ?? '' @@ -239,6 +234,10 @@ export default defineComponent({ return label }, + versionHumanExplicitDate(): string { + return moment(this.version.mtime).format('LLLL') + }, + downloadURL(): string { if (this.isCurrent) { return getRootUrl() + joinPaths('/remote.php/webdav', this.fileInfo.path, this.fileInfo.name) @@ -247,10 +246,6 @@ export default defineComponent({ } }, - formattedDate(): string { - return moment(this.version.mtime).format('LLL') - }, - enableLabeling(): boolean { return this.capabilities.files.version_labeling === true }, @@ -277,7 +272,7 @@ export default defineComponent({ const downloadAttribute = this.fileInfo.shareAttributes .find((attribute) => attribute.scope === 'permissions' && attribute.key === 'download') || {} // If the download attribute is set to false, the file is not downloadable - if (downloadAttribute?.enabled === false) { + if (downloadAttribute?.value === false) { return false } } @@ -286,6 +281,10 @@ export default defineComponent({ }, }, + created() { + this.fetchDisplayName() + }, + methods: { labelUpdate() { this.$emit('label-update-request') @@ -304,21 +303,26 @@ export default defineComponent({ }, async fetchDisplayName() { - // check to make sure that we have a valid author - in case database did not migrate, null author, etc. - if (this.version.author) { + this.versionAuthor = null + if (!this.version.author) { + return + } + + if (this.version.author === getCurrentUser()?.uid) { + this.versionAuthor = t('files_versions', 'You') + } else { try { - const { data } = await axios.get(generateOcsUrl(`/cloud/users/${this.version.author}`)) - this.versionAuthor = data.ocs.data.displayname - } catch (e) { - // Promise got rejected - default to null author to not try to load author profile - this.versionAuthor = null + const { data } = await axios.post(generateUrl('/displaynames'), { users: [this.version.author] }) + this.versionAuthor = data.users[this.version.author] + } catch (error) { + logger.warn('Could not load user display name', { error }) } } }, click() { if (!this.canView) { - window.location = this.downloadURL + window.location.href = this.downloadURL return } this.$emit('click', { version: this.version }) @@ -348,14 +352,30 @@ export default defineComponent({ gap: 0.5rem; color: var(--color-main-text); font-weight: 500; + overflow: hidden; &__label { font-weight: 700; + // Fix overflow on narrow screens + overflow: hidden; + text-overflow: ellipsis; + min-width: 110px; + } + + &__author_name { + overflow: hidden; + text-overflow: ellipsis; + } + + &__date { + // Fix overflow on narrow screens + overflow: hidden; + text-overflow: ellipsis; } &__subline { - color: var(--color-text-maxcontrast) - } + color: var(--color-text-maxcontrast) + } } &__image { diff --git a/apps/files_versions/src/components/VersionLabelDialog.vue b/apps/files_versions/src/components/VersionLabelDialog.vue new file mode 100644 index 00000000000..760780cae61 --- /dev/null +++ b/apps/files_versions/src/components/VersionLabelDialog.vue @@ -0,0 +1,123 @@ +<!-- + - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> +<template> + <NcDialog :buttons="dialogButtons" + content-classes="version-label-modal" + is-form + :open="open" + size="normal" + :name="t('files_versions', 'Name this version')" + @update:open="$emit('update:open', $event)" + @submit="setVersionLabel(editedVersionLabel)"> + <NcTextField ref="labelInput" + class="version-label-modal__input" + :label="t('files_versions', 'Version name')" + :placeholder="t('files_versions', 'Version name')" + :value.sync="editedVersionLabel" /> + + <p class="version-label-modal__info"> + {{ t('files_versions', 'Named versions are persisted, and excluded from automatic cleanups when your storage quota is full.') }} + </p> + </NcDialog> +</template> + +<script lang="ts"> +import { t } from '@nextcloud/l10n' +import { defineComponent } from 'vue' +import svgCheck from '@mdi/svg/svg/check.svg?raw' + +import NcDialog from '@nextcloud/vue/components/NcDialog' +import NcTextField from '@nextcloud/vue/components/NcTextField' + +type Focusable = Vue & { focus: () => void } + +export default defineComponent({ + name: 'VersionLabelDialog', + components: { + NcDialog, + NcTextField, + }, + props: { + open: { + type: Boolean, + default: false, + }, + versionLabel: { + type: String, + default: '', + }, + }, + data() { + return { + editedVersionLabel: '', + } + }, + computed: { + dialogButtons() { + const buttons: unknown[] = [] + if (this.versionLabel.trim() === '') { + // If there is no label just offer a cancel action that just closes the dialog + buttons.push({ + label: t('files_versions', 'Cancel'), + }) + } else { + // If there is already a label set, offer to remove the version label + buttons.push({ + label: t('files_versions', 'Remove version name'), + type: 'error', + nativeType: 'reset', + callback: () => { this.setVersionLabel('') }, + }) + } + return [ + ...buttons, + { + label: t('files_versions', 'Save version name'), + type: 'primary', + nativeType: 'submit', + icon: svgCheck, + }, + ] + }, + }, + watch: { + versionLabel: { + immediate: true, + handler(label) { + this.editedVersionLabel = label ?? '' + }, + }, + open: { + immediate: true, + handler(open) { + if (open) { + this.$nextTick(() => (this.$refs.labelInput as Focusable).focus()) + } + this.editedVersionLabel = this.versionLabel + }, + }, + }, + methods: { + setVersionLabel(label: string) { + this.$emit('label-update', label) + }, + + t, + }, +}) +</script> + +<style scoped lang="scss"> +.version-label-modal { + &__info { + color: var(--color-text-maxcontrast); + margin-block: calc(3 * var(--default-grid-baseline)); + } + + &__input { + margin-block-start: calc(2 * var(--default-grid-baseline)); + } +} +</style> diff --git a/apps/files_versions/src/components/VersionLabelForm.vue b/apps/files_versions/src/components/VersionLabelForm.vue deleted file mode 100644 index 5b8251b36c0..00000000000 --- a/apps/files_versions/src/components/VersionLabelForm.vue +++ /dev/null @@ -1,115 +0,0 @@ -<!-- - - @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/components/VirtualScrolling.vue b/apps/files_versions/src/components/VirtualScrolling.vue index fc199edd2ef..5a502036839 100644 --- a/apps/files_versions/src/components/VirtualScrolling.vue +++ b/apps/files_versions/src/components/VirtualScrolling.vue @@ -1,24 +1,7 @@ <!-- - - @copyright Copyright (c) 2022 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/>. - - - --> + - SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> <template> <div v-if="!useWindow && containerElement === null" ref="container" class="vs-container"> <div ref="rowsContainer" diff --git a/apps/files_versions/src/css/versions.css b/apps/files_versions/src/css/versions.css index 80aeaf4b88a..1637394ef48 100644 --- a/apps/files_versions/src/css/versions.css +++ b/apps/files_versions/src/css/versions.css @@ -1,3 +1,8 @@ +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2012-2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ .versionsTabView .clear-float { clear: both; } @@ -36,16 +41,16 @@ .versionsTabView img { cursor: pointer; - padding-right: 4px; + padding-inline-end: 4px; } .versionsTabView img.preview { position: relative; top: 6px; - left: 10px; + inset-inline-start: 10px; border: 1px solid var(--color-border-dark); cursor: default; - padding-right: 0; + padding-inline-end: 0; } .versionsTabView .version-container { @@ -58,7 +63,7 @@ } .versionsTabView .version-details { - text-align: left; + text-align: start; } .versionsTabView .version-details > span { @@ -68,7 +73,7 @@ .versionsTabView .revertVersion { cursor: pointer; float: right; - margin-right: 0; + margin-inline-end: 0; } .versionsTabView li.active .downloadVersion { @@ -90,7 +95,7 @@ } .version-container { - padding-left: 5px; + padding-inline-start: 5px; } .version-details { diff --git a/apps/files_versions/src/files_versions_tab.js b/apps/files_versions/src/files_versions_tab.js index a341b9009c3..12f36bad24a 100644 --- a/apps/files_versions/src/files_versions_tab.js +++ b/apps/files_versions/src/files_versions_tab.js @@ -1,34 +1,20 @@ /** - * @copyright 2022 Carl Schwan <carl@carlschwan.eu> - * @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/>. - * + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ import Vue from 'vue' import { translate as t, translatePlural as n } from '@nextcloud/l10n' import VersionTab from './views/VersionTab.vue' -import VTooltip from 'v-tooltip' +import VTooltipPlugin from 'v-tooltip' // eslint-disable-next-line n/no-missing-import, import/no-unresolved import BackupRestore from '@mdi/svg/svg/backup-restore.svg?raw' Vue.prototype.t = t Vue.prototype.n = n -Vue.use(VTooltip) +Vue.use(VTooltipPlugin) // Init Sharing tab component const View = Vue.extend(VersionTab) diff --git a/apps/files_versions/src/utils/davClient.js b/apps/files_versions/src/utils/davClient.js index b091935fc31..029373e9193 100644 --- a/apps/files_versions/src/utils/davClient.js +++ b/apps/files_versions/src/utils/davClient.js @@ -1,22 +1,6 @@ /** - * @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/>. + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ import { createClient } from 'webdav' @@ -30,16 +14,16 @@ const client = createClient(remote) // set CSRF token header const setHeaders = (token) => { - client.setHeaders({ - // Add this so the server knows it is an request from the browser - 'X-Requested-With': 'XMLHttpRequest', - // Inject user auth - requesttoken: token ?? '', - }) + client.setHeaders({ + // Add this so the server knows it is an request from the browser + 'X-Requested-With': 'XMLHttpRequest', + // Inject user auth + requesttoken: token ?? '', + }) } // refresh headers when request token changes onRequestTokenUpdate(setHeaders) setHeaders(getRequestToken()) -export default client
\ No newline at end of file +export default client diff --git a/apps/files_versions/src/utils/davRequest.js b/apps/files_versions/src/utils/davRequest.js index 93f1861006b..1dcf620564a 100644 --- a/apps/files_versions/src/utils/davRequest.js +++ b/apps/files_versions/src/utils/davRequest.js @@ -1,23 +1,6 @@ /** - * @copyright Copyright (c) 2019 Louis Chmn <louis@chmn.me> - * - * @author Louis Chmn <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/>. - * + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ export default `<?xml version="1.0"?> diff --git a/apps/files_versions/src/utils/logger.js b/apps/files_versions/src/utils/logger.js index 4f0356764d9..f84cb969244 100644 --- a/apps/files_versions/src/utils/logger.js +++ b/apps/files_versions/src/utils/logger.js @@ -1,22 +1,6 @@ /** - * @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/>. + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ import { getLoggerBuilder } from '@nextcloud/logger' diff --git a/apps/files_versions/src/utils/versions.ts b/apps/files_versions/src/utils/versions.ts index 4048f23c327..6d5933f0bd9 100644 --- a/apps/files_versions/src/utils/versions.ts +++ b/apps/files_versions/src/utils/versions.ts @@ -2,24 +2,8 @@ /* 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/>. + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ import type { FileStat, ResponseDataDetailed } from 'webdav' @@ -44,7 +28,6 @@ export interface Version { 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 @@ -94,12 +77,12 @@ function formatVersion(version: any, fileInfo: any): Version { 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', { + previewUrl = generateUrl('/core/preview?fileId={fileId}&c={fileEtag}&x=250&y=250&forceIcon=0&a=0&forceIcon=1&mimeFallback=1', { fileId: fileInfo.id, fileEtag: fileInfo.etag, }) } else { - previewUrl = generateUrl('/apps/files_versions/preview?file={file}&version={fileVersion}', { + previewUrl = generateUrl('/apps/files_versions/preview?file={file}&version={fileVersion}&mimeFallback=1', { file: joinPaths(fileInfo.path, fileInfo.name), fileVersion: version.basename, }) @@ -107,7 +90,8 @@ function formatVersion(version: any, fileInfo: any): Version { return { fileId: fileInfo.id, - label: version.props['version-label'], + // If version-label is defined make sure it is a string (prevent issue if the label is a number an PHP returns a number then) + label: version.props['version-label'] && String(version.props['version-label']), author: version.props['version-author'] ?? null, filename: version.filename, basename: moment(mtime).format('LLL'), @@ -117,7 +101,6 @@ function formatVersion(version: any, fileInfo: any): Version { type: version.type, mtime, permissions: 'R', - hasPreview: version.props['has-preview'] === 1, previewUrl, url: joinPaths('/remote.php/dav', version.filename), source: generateRemoteUrl('dav') + encodePath(version.filename), diff --git a/apps/files_versions/src/views/VersionTab.vue b/apps/files_versions/src/views/VersionTab.vue index 745b9d0f58e..a643aef439d 100644 --- a/apps/files_versions/src/views/VersionTab.vue +++ b/apps/files_versions/src/views/VersionTab.vue @@ -1,81 +1,68 @@ <!-- - - @copyright 2022 Carl Schwan <carl@carlschwan.eu> - - @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/>. - --> + - SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> <template> <div class="versions-tab__container"> - <VirtualScrolling :sections="sections" + <VirtualScrolling v-slot="{ visibleSections }" + :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> + <ul :aria-label="t('files_versions', 'File versions')" 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> <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> + <VersionLabelDialog v-if="editedVersion" + :open.sync="showVersionLabelForm" + :version-label="editedVersion.label" + @label-update="handleLabelUpdate" /> </div> </template> <script> import path from 'path' +import { getCurrentUser } from '@nextcloud/auth' 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/dist/Components/NcLoadingIcon.js' -import NcModal from '@nextcloud/vue/dist/Components/NcModal.js' +import { useIsMobile } from '@nextcloud/vue/composables/useIsMobile' +import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon' 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' +import VersionLabelDialog from '../components/VersionLabelDialog.vue' export default { name: 'VersionTab', components: { Version, VirtualScrolling, - VersionLabelForm, + VersionLabelDialog, NcLoadingIcon, - NcModal, }, - mixins: [ - isMobile, - ], + + setup() { + return { + isMobile: useIsMobile(), + } + }, + data() { return { fileInfo: null, @@ -84,6 +71,7 @@ export default { versions: [], loading: false, showVersionLabelForm: false, + editedVersion: null, } }, computed: { @@ -192,7 +180,7 @@ export default { /** * Handle restored event from Version.vue * - * @param {import('../utils/versions.ts').Version} version + * @param {import('../utils/versions.ts').Version} version The version to restore */ async handleRestore(version) { // Update local copy of fileInfo as rendering depends on it. @@ -215,7 +203,7 @@ export default { try { await restoreVersion(version) - if (version.label !== '') { + if (version.label) { showSuccess(t('files_versions', `${version.label} restored`)) } else if (version.mtime === this.initialVersionMtime) { showSuccess(t('files_versions', 'Initial version restored')) @@ -232,7 +220,7 @@ export default { /** * Handle label-updated event from Version.vue - * @param {import('../utils/versions.ts').Version} version + * @param {import('../utils/versions.ts').Version} version The version to update */ handleLabelUpdateRequest(version) { this.showVersionLabelForm = true @@ -241,7 +229,7 @@ export default { /** * Handle label-updated event from Version.vue - * @param {string} newLabel + * @param {string} newLabel The new label */ async handleLabelUpdate(newLabel) { const oldLabel = this.editedVersion.label @@ -261,8 +249,7 @@ export default { /** * Handle deleted event from Version.vue * - * @param {import('../utils/versions.ts').Version} version - * @param {string} newName + * @param {import('../utils/versions.ts').Version} version The version to delete */ async handleDelete(version) { const index = this.versions.indexOf(version) @@ -290,13 +277,12 @@ export default { return } - // Versions previews are too small for our use case, so we override hasPreview and previewUrl + // Versions previews are too small for our use case, so we override previewUrl // which makes the viewer render the original file. // We also point to the original filename if the version is the current one. const versions = this.versions.map(version => ({ ...version, filename: version.mtime === this.fileInfo.mtime ? path.join('files', getCurrentUser()?.uid ?? '', this.fileInfo.path, this.fileInfo.name) : version.filename, - hasPreview: false, previewUrl: undefined, })) @@ -307,7 +293,7 @@ export default { }, compareVersion({ version }) { - const versions = this.versions.map(version => ({ ...version, hasPreview: false, previewUrl: undefined })) + const versions = this.versions.map(version => ({ ...version, previewUrl: undefined })) OCA.Viewer.compare(this.viewerFileInfo, versions.find(v => v.source === version.source)) }, |