diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2025-05-22 17:01:48 +0200 |
---|---|---|
committer | Ferdinand Thiessen <opensource@fthiessen.de> | 2025-05-26 15:11:36 +0200 |
commit | 60ccc85e7685c9375365230bb53cf20d15bc31e8 (patch) | |
tree | d6b1cc3816a5e029421d032b6413a534d571cc6c | |
parent | 27149b7f247490eb9d3c6018ce7b2ea61b29000b (diff) | |
download | nextcloud-server-60ccc85e7685c9375365230bb53cf20d15bc31e8.tar.gz nextcloud-server-60ccc85e7685c9375365230bb53cf20d15bc31e8.zip |
fix(files_sharing): show note, label and list of uploaded files on file drop
This was missing from the Vue migration of the public share view:
- Show the note as the description of the file drop
- Show the label as the heading of the file drop if available
- Show list of uploaded files for verification
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
5 files changed, 99 insertions, 19 deletions
diff --git a/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php b/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php index 910d00a5972..645250ab2b5 100644 --- a/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php +++ b/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php @@ -91,6 +91,9 @@ class DefaultPublicShareTemplateProvider implements IPublicShareTemplateProvider 'disclaimer', $this->appConfig->getValueString('core', 'shareapi_public_link_disclaimertext'), ); + // file drops do not request the root folder so we need to provide label and note if available + $this->initialState->provideInitialState('label', $share->getLabel()); + $this->initialState->provideInitialState('note', $share->getNote()); } // Set up initial state $this->initialState->provideInitialState('isPublic', true); diff --git a/apps/files_sharing/src/files_views/publicFileDrop.ts b/apps/files_sharing/src/files_views/publicFileDrop.ts index 0d782d48fc7..65756e83c74 100644 --- a/apps/files_sharing/src/files_views/publicFileDrop.ts +++ b/apps/files_sharing/src/files_views/publicFileDrop.ts @@ -4,7 +4,8 @@ */ import type { VueConstructor } from 'vue' -import { Folder, Permission, View, davRemoteURL, davRootPath, getNavigation } from '@nextcloud/files' +import { Folder, Permission, View, getNavigation } from '@nextcloud/files' +import { defaultRemoteURL, defaultRootPath } from '@nextcloud/files/dav' import { loadState } from '@nextcloud/initial-state' import { translate as t } from '@nextcloud/l10n' import svgCloudUpload from '@mdi/svg/svg/cloud-upload.svg?raw' @@ -45,8 +46,8 @@ export default () => { // Fake a writeonly folder as root folder: new Folder({ id: 0, - source: `${davRemoteURL}${davRootPath}`, - root: davRootPath, + source: `${defaultRemoteURL}${defaultRootPath}`, + root: defaultRootPath, owner: null, permissions: Permission.CREATE, }), diff --git a/apps/files_sharing/src/views/FilesViewFileDropEmptyContent.vue b/apps/files_sharing/src/views/FilesViewFileDropEmptyContent.vue index 5571e5e9f5d..33fec9af028 100644 --- a/apps/files_sharing/src/views/FilesViewFileDropEmptyContent.vue +++ b/apps/files_sharing/src/views/FilesViewFileDropEmptyContent.vue @@ -5,13 +5,29 @@ <template> <NcEmptyContent class="file-drop-empty-content" data-cy-files-sharing-file-drop - :name="t('files_sharing', 'File drop')"> + :name="name"> <template #icon> <NcIconSvgWrapper :svg="svgCloudUpload" /> </template> <template #description> - {{ t('files_sharing', 'Upload files to {foldername}.', { foldername }) }} - {{ disclaimer === '' ? '' : t('files_sharing', 'By uploading files, you agree to the terms of service.') }} + <p> + {{ shareNote || t('files_sharing', 'Upload files to {foldername}.', { foldername }) }} + </p> + <p v-if="disclaimer"> + {{ t('files_sharing', 'By uploading files, you agree to the terms of service.') }} + </p> + <NcNoteCard v-if="getSortedUploads().length" + class="file-drop-empty-content__note-card" + type="success"> + <h2 id="file-drop-empty-content__heading"> + {{ t('files_sharing', 'Successfully uploaded files') }} + </h2> + <ul aria-labelledby="file-drop-empty-content__heading" class="file-drop-empty-content__list"> + <li v-for="file in getSortedUploads()" :key="file"> + {{ file }} + </li> + </ul> + </NcNoteCard> </template> <template #action> <template v-if="disclaimer"> @@ -34,16 +50,24 @@ </NcEmptyContent> </template> +<script lang="ts"> +/* eslint-disable import/first */ + +// We need this on module level rather than on the instance as view will be refreshed by the files app after uploading +const uploads = new Set<string>() +</script> + <script setup lang="ts"> import { loadState } from '@nextcloud/initial-state' import { translate as t } from '@nextcloud/l10n' -import { getUploader, UploadPicker } from '@nextcloud/upload' +import { getUploader, UploadPicker, UploadStatus } from '@nextcloud/upload' import { ref } from 'vue' import NcButton from '@nextcloud/vue/components/NcButton' import NcDialog from '@nextcloud/vue/components/NcDialog' import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent' import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper' +import NcNoteCard from '@nextcloud/vue/components/NcNoteCard' import svgCloudUpload from '@mdi/svg/svg/cloud-upload.svg?raw' defineProps<{ @@ -51,17 +75,62 @@ defineProps<{ }>() const disclaimer = loadState<string>('files_sharing', 'disclaimer', '') +const shareLabel = loadState<string>('files_sharing', 'label', '') +const shareNote = loadState<string>('files_sharing', 'note', '') + +const name = shareLabel || t('files_sharing', 'File drop') + const showDialog = ref(false) const uploadDestination = getUploader().destination -</script> -<style scoped> -:deep(.terms-of-service-dialog) { - min-height: min(100px, 20vh); +getUploader() + .addNotifier((upload) => { + if (upload.status === UploadStatus.FINISHED && upload.file.name) { + // if a upload is finished and is not a meta upload (name is set) + // then we add the upload to the list of finished uploads to be shown to the user + uploads.add(upload.file.name) + } + }) + +/** + * Get the previous uploads as sorted list + */ +function getSortedUploads() { + return [...uploads].sort((a, b) => a.localeCompare(b)) } -/* TODO fix in library */ -.file-drop-empty-content :deep(.empty-content__action) { - display: flex; - gap: var(--default-grid-baseline); +</script> + +<style scoped lang="scss"> +.file-drop-empty-content { + margin: auto; + max-width: max(50vw, 300px); + + .file-drop-empty-content__note-card { + width: fit-content; + margin-inline: auto; + } + + #file-drop-empty-content__heading { + margin-block: 0 10px; + font-weight: bold; + font-size: 20px; + } + + .file-drop-empty-content__list { + list-style: inside; + max-height: min(350px, 33vh); + overflow-y: scroll; + padding-inline-end: calc(2 * var(--default-grid-baseline)); + } + + :deep(.terms-of-service-dialog) { + min-height: min(100px, 20vh); + } + + /* TODO fix in library */ + :deep(.empty-content__action) { + display: flex; + gap: var(--default-grid-baseline); + } } </style> diff --git a/apps/files_sharing/tests/Controller/ShareControllerTest.php b/apps/files_sharing/tests/Controller/ShareControllerTest.php index 524336787cb..a6bef1bed56 100644 --- a/apps/files_sharing/tests/Controller/ShareControllerTest.php +++ b/apps/files_sharing/tests/Controller/ShareControllerTest.php @@ -399,6 +399,8 @@ class ShareControllerTest extends \Test\TestCase { ->setPassword('password') ->setShareOwner('ownerUID') ->setSharedBy('initiatorUID') + ->setNote('The note') + ->setLabel('A label') ->setNode($file) ->setTarget("/$filename") ->setToken('token'); @@ -478,6 +480,8 @@ class ShareControllerTest extends \Test\TestCase { 'disclaimer' => 'My disclaimer text', 'owner' => 'ownerUID', 'ownerDisplayName' => 'ownerDisplay', + 'note' => 'The note', + 'label' => 'A label', ]; $response = $this->shareController->showShare(); @@ -487,9 +491,9 @@ class ShareControllerTest extends \Test\TestCase { $csp = new ContentSecurityPolicy(); $csp->addAllowedFrameDomain('\'self\''); $expectedResponse = new PublicTemplateResponse('files', 'index'); - $expectedResponse->setParams(['pageTitle' => $filename]); + $expectedResponse->setParams(['pageTitle' => 'A label']); $expectedResponse->setContentSecurityPolicy($csp); - $expectedResponse->setHeaderTitle($filename); + $expectedResponse->setHeaderTitle('A label'); $expectedResponse->setHeaderDetails('shared by ownerDisplay'); $expectedResponse->setHeaderActions([ new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', 'shareUrl'), diff --git a/cypress/e2e/files_sharing/public-share/view_file-drop.cy.ts b/cypress/e2e/files_sharing/public-share/view_file-drop.cy.ts index 3a38f0c9ec7..f95115ee591 100644 --- a/cypress/e2e/files_sharing/public-share/view_file-drop.cy.ts +++ b/cypress/e2e/files_sharing/public-share/view_file-drop.cy.ts @@ -149,9 +149,12 @@ describe('files_sharing: Public share - File drop', { testIsolation: true }, () after(() => cy.runOccCommand('config:app:delete core shareapi_public_link_disclaimertext')) it('shows ToS on file-drop view', () => { - cy.contains(`Upload files to ${shareName}`) + cy.get('[data-cy-files-sharing-file-drop]') + .contains(`Upload files to ${shareName}`) + .should('be.visible') + cy.get('[data-cy-files-sharing-file-drop]') + .contains('agree to the terms of service') .should('be.visible') - .should('contain.text', 'agree to the terms of service') cy.findByRole('button', { name: /Terms of service/i }) .should('be.visible') .click() |