From a121ef7d09adadefcce2a90dec96e973e471bf90 Mon Sep 17 00:00:00 2001 From: steven-mpawulo Date: Mon, 13 Jul 2026 10:51:25 +0300 Subject: [PATCH 1/5] feat: adds file location link in toast Signed-off-by: steven-mpawulo --- lib/Controller/MessagesController.php | 12 +++++++++++- src/components/MessageAttachment.vue | 16 ++++++++++++++-- src/service/AttachmentService.js | 4 +++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/Controller/MessagesController.php b/lib/Controller/MessagesController.php index 2e9c0a0648..1215f58128 100755 --- a/lib/Controller/MessagesController.php +++ b/lib/Controller/MessagesController.php @@ -858,6 +858,7 @@ public function saveAttachment(int $id, ); } + $lastFile = null; foreach ($attachments as $attachment) { $fileName = $attachment->getName() ?? $this->l10n->t('Embedded message %s', [ $attachment->getId(), @@ -874,8 +875,17 @@ public function saveAttachment(int $id, $newFile = $this->userFolder->newFile($fullPath); $newFile->putContent($attachment->getContent()); + $lastFile = $newFile; } - return new JSONResponse(); + + if ($lastFile === null) { + return new JSONResponse(['fileId' => null, 'path' => null]); + } + + return new JSONResponse([ + 'fileId' => $lastFile->getId(), + 'path' => $targetPath, + ]); } /** diff --git a/src/components/MessageAttachment.vue b/src/components/MessageAttachment.vue index 8fb911bdcd..763e21a1e6 100644 --- a/src/components/MessageAttachment.vue +++ b/src/components/MessageAttachment.vue @@ -85,6 +85,7 @@ import { showError, showSuccess } from '@nextcloud/dialogs' import { FilePickerVue as FilePicker } from '@nextcloud/dialogs/filepicker.js' import { formatFileSize } from '@nextcloud/files' import { translate as t } from '@nextcloud/l10n' +import { generateUrl } from '@nextcloud/router' import { NcActionButton as ActionButton, NcActions as Actions, NcLoadingIcon as IconLoading } from '@nextcloud/vue' import IconArrow from 'vue-material-design-icons/ArrowLeft.vue' import IconSave from 'vue-material-design-icons/FolderOutline.vue' @@ -231,9 +232,20 @@ export default { const id = this.$route.params.threadId try { - await saveAttachmentToFiles(id, this.id, path) + const saved = await saveAttachmentToFiles(id, this.id, path) Logger.info('saved') - showSuccess(t('mail', 'Attachment saved to Files')) + + const fileLocationUrl = generateUrl('/apps/files/files/{fileId}?dir={dir}&openfile=true', { + fileId: saved?.fileId, + dir: saved?.path, + }) + + showSuccess(t('mail', `Attachment saved to Files + ${fileLocationUrl}`), { + onClick: () => { + window.open(fileLocationUrl, '_blank') + }, + }) } catch (e) { Logger.error('not saved', { error: e }) showError(t('mail', 'Attachment could not be saved')) diff --git a/src/service/AttachmentService.js b/src/service/AttachmentService.js index a09cc36617..9c69890f8d 100644 --- a/src/service/AttachmentService.js +++ b/src/service/AttachmentService.js @@ -15,9 +15,11 @@ export async function saveAttachmentToFiles(id, attachmentId, directory) { }, ) - return await Axios.post(url, { + const res = await Axios.post(url, { targetPath: directory, }) + + return res.data } export async function saveAttachmentsToFiles(id, directory) { From 2842da467575f800706f6e29fddb280dae1df91c Mon Sep 17 00:00:00 2001 From: steven-mpawulo Date: Mon, 13 Jul 2026 11:49:15 +0300 Subject: [PATCH 2/5] fix updates test case for saving single attachment to files Signed-off-by: steven-mpawulo --- tests/Unit/Controller/MessagesControllerTest.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/Unit/Controller/MessagesControllerTest.php b/tests/Unit/Controller/MessagesControllerTest.php index 5651124b18..c0d24d84b5 100644 --- a/tests/Unit/Controller/MessagesControllerTest.php +++ b/tests/Unit/Controller/MessagesControllerTest.php @@ -398,7 +398,10 @@ public function testSaveSingleAttachment() { ->method('getContent') ->will($this->returnValue('abcdefg')); - $expected = new JSONResponse(); + $expected = new JSONResponse([ + 'fileId' => $file->getId(), + 'path' => $targetPath, + ]); $response = $this->controller->saveAttachment( $id, $attachmentId, From 32946a594781cebc0be0aea05ce30cdf81e5f067 Mon Sep 17 00:00:00 2001 From: steven-mpawulo Date: Wed, 15 Jul 2026 10:08:03 +0300 Subject: [PATCH 3/5] fix: modifies the message displayed in toast Signed-off-by: steven-mpawulo --- src/components/MessageAttachment.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MessageAttachment.vue b/src/components/MessageAttachment.vue index 763e21a1e6..36d2cb46aa 100644 --- a/src/components/MessageAttachment.vue +++ b/src/components/MessageAttachment.vue @@ -240,8 +240,8 @@ export default { dir: saved?.path, }) - showSuccess(t('mail', `Attachment saved to Files - ${fileLocationUrl}`), { + showSuccess(t('mail', '

Attachment saved to files

Go to Files

'), { + isHTML: true, onClick: () => { window.open(fileLocationUrl, '_blank') }, From 3bf97f7f4f35e331b8549ca437c8991abc2d9f2a Mon Sep 17 00:00:00 2001 From: steven-mpawulo Date: Thu, 16 Jul 2026 10:26:47 +0300 Subject: [PATCH 4/5] fix: use anchor tag instead of onClick handler for file location link Signed-off-by: steven-mpawulo --- src/components/MessageAttachment.vue | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/MessageAttachment.vue b/src/components/MessageAttachment.vue index 36d2cb46aa..cacbe94bc4 100644 --- a/src/components/MessageAttachment.vue +++ b/src/components/MessageAttachment.vue @@ -240,11 +240,10 @@ export default { dir: saved?.path, }) - showSuccess(t('mail', '

Attachment saved to files

Go to Files

'), { + showSuccess(t('mail', '

Attachment saved to Files

Go to Files

', { + fileLocationUrl, + }), { isHTML: true, - onClick: () => { - window.open(fileLocationUrl, '_blank') - }, }) } catch (e) { Logger.error('not saved', { error: e }) From 03abca325ef5181d32e3a27a5f4c6569e5dd478c Mon Sep 17 00:00:00 2001 From: steven-mpawulo Date: Thu, 16 Jul 2026 10:52:03 +0300 Subject: [PATCH 5/5] fix: separate translatable strings and HTML-escape them in file location toast Signed-off-by: steven-mpawulo --- src/components/MessageAttachment.vue | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/MessageAttachment.vue b/src/components/MessageAttachment.vue index cacbe94bc4..7c1b3583c0 100644 --- a/src/components/MessageAttachment.vue +++ b/src/components/MessageAttachment.vue @@ -87,6 +87,7 @@ import { formatFileSize } from '@nextcloud/files' import { translate as t } from '@nextcloud/l10n' import { generateUrl } from '@nextcloud/router' import { NcActionButton as ActionButton, NcActions as Actions, NcLoadingIcon as IconLoading } from '@nextcloud/vue' +import escapeHtml from 'escape-html' import IconArrow from 'vue-material-design-icons/ArrowLeft.vue' import IconSave from 'vue-material-design-icons/FolderOutline.vue' import IconAdd from 'vue-material-design-icons/Plus.vue' @@ -240,9 +241,10 @@ export default { dir: saved?.path, }) - showSuccess(t('mail', '

Attachment saved to Files

Go to Files

', { - fileLocationUrl, - }), { + const savedText = t('mail', 'Attachment saved to Files') + const goToFilesText = t('mail', 'Go to Files') + + showSuccess(`

${escapeHtml(savedText)}

${escapeHtml(goToFilesText)}

`, { isHTML: true, }) } catch (e) {