From 34dc636d135590fb57700c15799da078ec54785b Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 18 Jun 2026 19:07:39 -0400 Subject: [PATCH] feat: attachment preview Signed-off-by: Sebastian --- src/components/MessageReader.vue | 67 ++++++++++++++++++++++++-- src/components/reader/ReaderHeader.vue | 64 +++++++++++++++++++++--- src/services/mediaViewer.ts | 48 ++++++++++++++++++ src/stores/mailStore.ts | 17 ++++++- src/stores/mailUiStore.ts | 5 ++ src/types/mediaViewer.ts | 9 ++++ 6 files changed, 197 insertions(+), 13 deletions(-) create mode 100644 src/services/mediaViewer.ts create mode 100644 src/types/mediaViewer.ts diff --git a/src/components/MessageReader.vue b/src/components/MessageReader.vue index 3a11cb8..0467ffc 100644 --- a/src/components/MessageReader.vue +++ b/src/components/MessageReader.vue @@ -1,9 +1,11 @@ diff --git a/src/components/reader/ReaderHeader.vue b/src/components/reader/ReaderHeader.vue index 4c04f30..0c7a826 100644 --- a/src/components/reader/ReaderHeader.vue +++ b/src/components/reader/ReaderHeader.vue @@ -2,7 +2,18 @@ import { computed } from 'vue' import RecipientDetails from '@/components/common/RecipientDetails.vue' import { formatFileSize } from '@/utile/format' +import { mediaViewerCanDisplay, mediaViewerComponent } from '@/services/mediaViewer' +import { useMailUiStore } from '@/stores/mailUiStore' import type { EntityObject } from '@MailManager/models'; +import type { MessagePartInterface } from '@MailManager/types/message'; + +interface AttachmentItem { + id: string + title: string + mime: string + size?: number | null + meta: { index: number; attachment: MessagePartInterface } +} interface Props { entity: EntityObject | null @@ -11,9 +22,13 @@ interface Props { const props = defineProps() const emit = defineEmits<{ - downloadAttachment: [index: number] + download: [index: number] + preview: [index: number] }>() +const mailUiStore = useMailUiStore() +const previewPopover = computed(() => mediaViewerComponent('popover')) + const message = computed(() => { return props.entity?.properties ?? null }) @@ -22,6 +37,33 @@ const randomKey = computed(() => { return Math.random().toString(36).substring(2, 15) }) +const attachmentItems = computed(() => + (message.value?.attachments ?? []).map((attachment, index) => ({ + id: attachment.partId || attachment.blobId || attachment.cid || `att-${index}`, + title: attachment.name || 'Attachment', + mime: attachment.type || '', + size: attachment.size ?? null, + meta: { index, attachment }, + })), +) + +const isPreviewable = (attachment: MessagePartInterface): boolean => { + return mediaViewerCanDisplay(attachment.type) +} + +const handleAttachmentClick = (attachment: MessagePartInterface, index: number): void => { + if (isPreviewable(attachment)) { + emit('preview', index) + } else { + emit('download', index) + } +} + +const handleViewerRetrieve = (item: AttachmentItem): Promise => { + if (!props.entity) return Promise.reject(new Error('No message selected')) + return mailUiStore.attachmentBlob(props.entity, item.meta.attachment) +} + // Format date for display const formatDate = (date: Date | string | null | undefined): string => { if (!date) return '' @@ -37,10 +79,6 @@ const formatDate = (date: Date | string | null | undefined): string => { hour12: true }) } - -const download = async (index: number): Promise => { - emit('downloadAttachment', index) -}