+
diff --git a/src/components/MessageAttachments.vue b/src/components/MessageAttachments.vue
index 6ba80a83f1..e08eb7c0d9 100644
--- a/src/components/MessageAttachments.vue
+++ b/src/components/MessageAttachments.vue
@@ -66,7 +66,6 @@
diff --git a/src/components/icons/FileIcon.vue b/src/components/icons/FileIcon.vue
new file mode 100644
index 0000000000..b290c1d166
--- /dev/null
+++ b/src/components/icons/FileIcon.vue
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/mixins/AttachementMixin.js b/src/mixins/AttachementMixin.js
new file mode 100644
index 0000000000..30c47b66d2
--- /dev/null
+++ b/src/mixins/AttachementMixin.js
@@ -0,0 +1,44 @@
+/**
+ * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+export default {
+ computed: {
+ fileInfos() {
+ return this.attachments.map((attachment) => ({
+ filename: attachment.downloadUrl,
+ source: attachment.downloadUrl,
+ basename: attachment.fileName,
+ mime: attachment.mime,
+ etag: 'fixme',
+ hasPreview: false,
+ fileid: parseInt(attachment.id, 10),
+ }))
+ },
+
+ previewableFileInfos() {
+ return this.fileInfos.filter((fileInfo) => (fileInfo.mime.startsWith('image/')
+ || fileInfo.mime.startsWith('video/')
+ || fileInfo.mime.startsWith('audio/')
+ || fileInfo.mime === 'application/pdf')
+ && OCA.Viewer.mimetypes.includes(fileInfo.mime))
+ },
+ },
+ methods: {
+ canPreview(fileInfo) {
+ return this.previewableFileInfos.includes(fileInfo)
+ },
+ showViewer(fileInfo) {
+ if (!this.canPreview(fileInfo)) {
+ return
+ }
+
+ OCA.Viewer.open({
+ fileInfo,
+ list: this.previewableFileInfos,
+ })
+ },
+
+ },
+}
diff --git a/src/store/constants.js b/src/store/constants.js
index d34e342e58..ee76ba8f4e 100644
--- a/src/store/constants.js
+++ b/src/store/constants.js
@@ -18,3 +18,6 @@ export const STATUS_IMAP_SENT_MAILBOX_FAIL = 11
export const STATUS_SMTP_ERROR = 13
export const FOLLOW_UP_TAG_LABEL = '$follow_up'
+export const FILE_EXTENSIONS_WORD_PROCESSING = ['doc', 'docx', 'dot', 'odt', 'dotx', 'odt', 'ott']
+export const FILE_EXTENSIONS_SPREADSHEET = ['xls', 'xlsx', 'ods']
+export const FILE_EXTENSIONS_PRESENTATION = ['ppt', 'pptx', 'odp', 'otp', 'pps', 'ppsx', 'pot', 'potx']
diff --git a/src/store/mainStore/actions.js b/src/store/mainStore/actions.js
index 83059563be..6429cc2ebe 100644
--- a/src/store/mainStore/actions.js
+++ b/src/store/mainStore/actions.js
@@ -2225,7 +2225,13 @@ export default function mainStoreActions() {
this.normalizeTags(e)
const mailbox = this.mailboxes[e.mailboxId]
Vue.set(e, 'accountId', mailbox.accountId)
- Vue.set(this.envelopes, e.databaseId, { ...this.envelopes[e.databaseId] || {}, ...e })
+ const existing = this.envelopes[e.databaseId] || {}
+ const merged = { ...existing, ...e }
+ // preserve attachments
+ if (existing.attachments && existing.attachments.length > 0) {
+ merged.attachments = existing.attachments
+ }
+ Vue.set(this.envelopes, e.databaseId, merged)
})
// Store the references
diff --git a/src/tests/unit/store/mutations.spec.js b/src/tests/unit/store/mutations.spec.js
index acc33761b3..55bacd934b 100644
--- a/src/tests/unit/store/mutations.spec.js
+++ b/src/tests/unit/store/mutations.spec.js
@@ -939,6 +939,7 @@ describe('Pinia store mutations', () => {
databaseId: 123,
mailboxId: 27,
uid: 12345,
+ attachments: [{ id: 1, fileName: 'example' }],
}
store.$patch({
mailboxes: {
@@ -966,6 +967,7 @@ describe('Pinia store mutations', () => {
databaseId: 123,
mailboxId: 27,
uid: 12345,
+ attachments: [],
},
{
databaseId: 124,
@@ -1001,6 +1003,7 @@ describe('Pinia store mutations', () => {
124,
],
tags: [],
+ attachments: [{ id: 1, fileName: 'example' }],
},
124: {
databaseId: 124,
@@ -1042,6 +1045,7 @@ describe('Pinia store mutations', () => {
envelopes: [{
mailboxId: 27,
databaseId: 12345,
+ attachments: [],
id: 123,
subject: 'henlo',
uid: 321,
@@ -1070,6 +1074,7 @@ describe('Pinia store mutations', () => {
12345: {
mailboxId: 27,
databaseId: 12345,
+ attachments: [],
uid: 321,
id: 123,
subject: 'henlo',