feat: recipient details #46
@@ -10,14 +10,16 @@ import Placeholder from '@tiptap/extension-placeholder'
|
|||||||
import { EntityObject } from '@MailManager/models/entity'
|
import { EntityObject } from '@MailManager/models/entity'
|
||||||
import type { CollectionObject } from '@MailManager/models'
|
import type { CollectionObject } from '@MailManager/models'
|
||||||
import { useMailStore } from '@/stores/mailStore'
|
import { useMailStore } from '@/stores/mailStore'
|
||||||
|
import type { MessageAddressInterface } from '@MailManager/types/message'
|
||||||
|
import { ComposerMode } from '@/types/composer'
|
||||||
import ComposerToolbar from '@/components/composer/ComposerToolbar.vue'
|
import ComposerToolbar from '@/components/composer/ComposerToolbar.vue'
|
||||||
import ComposerRecipients from '@/components/composer/ComposerRecipients.vue'
|
import ComposerRecipients from '@/components/composer/ComposerRecipients.vue'
|
||||||
import ComposerEditor from '@/components/composer/ComposerEditor.vue'
|
import ComposerEditor from '@/components/composer/ComposerEditor.vue'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
mode: 'new' | 'reply' | 'forward'
|
mode: ComposerMode
|
||||||
source?: EntityObject | null
|
source?: EntityObject | MessageAddressInterface | null
|
||||||
folder?: CollectionObject | null
|
folder?: CollectionObject | null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +85,15 @@ function initializeComposerFromProps() {
|
|||||||
mailStore.resetComposerState()
|
mailStore.resetComposerState()
|
||||||
resetComposerFields()
|
resetComposerFields()
|
||||||
|
|
||||||
if (!props.source) {
|
if (props.mode === ComposerMode.Fresh) {
|
||||||
|
if (props.source && 'address' in props.source) {
|
||||||
|
// If source is an email address, pre-fill the "To" field
|
||||||
|
to.value = [props.source.address]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.source instanceof EntityObject == false) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,19 +104,19 @@ function initializeComposerFromProps() {
|
|||||||
const sentAt = sourceMessage.sent || props.source.created || ''
|
const sentAt = sourceMessage.sent || props.source.created || ''
|
||||||
const sentLabel = sentAt ? new Date(sentAt).toLocaleString() : 'an unknown time'
|
const sentLabel = sentAt ? new Date(sentAt).toLocaleString() : 'an unknown time'
|
||||||
|
|
||||||
if (props.mode === 'reply') {
|
if (props.mode === ComposerMode.Reply) {
|
||||||
const fromEmail = sourceMessage.replyTo?.[0]?.address || sourceMessage.from?.address
|
const fromEmail = sourceMessage.replyTo?.[0]?.address || sourceMessage.from?.address
|
||||||
to.value = fromEmail ? [fromEmail] : []
|
to.value = fromEmail ? [fromEmail] : []
|
||||||
subject.value = /^Re:/i.test(originalSubject)
|
subject.value = /^Re:/i.test(originalSubject)
|
||||||
? originalSubject
|
? originalSubject
|
||||||
: `Re: ${originalSubject}`
|
: `Re: ${originalSubject}`
|
||||||
editor.value?.commands.setContent(
|
editor.value?.commands.setContent(
|
||||||
`<p><br></p><p>On ${sentLabel}, ${senderName} wrote:</p><blockquote>${originalBody}</blockquote>`,
|
`<p><br></p><p>---------- Original message ---------</p><p>From: ${senderName}</p><p>Date: ${sentLabel}</p><p>Subject: ${originalSubject}</p><blockquote>${originalBody}</blockquote>`,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.mode === 'forward') {
|
if (props.mode === ComposerMode.Forward) {
|
||||||
subject.value = /^Fwd:/i.test(originalSubject)
|
subject.value = /^Fwd:/i.test(originalSubject)
|
||||||
? originalSubject
|
? originalSubject
|
||||||
: `Fwd: ${originalSubject}`
|
: `Fwd: ${originalSubject}`
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { computed, onBeforeUnmount, ref } from 'vue'
|
|||||||
import type { EntityIdentifier } from '@MailManager/types/common'
|
import type { EntityIdentifier } from '@MailManager/types/common'
|
||||||
import type { EntityObject } from '@MailManager/models'
|
import type { EntityObject } from '@MailManager/models'
|
||||||
import type { CollectionObject } from '@MailManager/models/collection'
|
import type { CollectionObject } from '@MailManager/models/collection'
|
||||||
|
import RecipientDetails from '@/components/common/RecipientDetails.vue'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -363,7 +364,11 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
<v-list-item-title class="d-flex align-center">
|
<v-list-item-title class="d-flex align-center">
|
||||||
<span class="flex-grow-1 text-truncate">
|
<span class="flex-grow-1 text-truncate">
|
||||||
{{ message.properties.from?.label || message.properties.from?.address || 'Unknown Sender' }}
|
<RecipientDetails :address="message.properties.from">
|
||||||
|
<template #default="{ label }">
|
||||||
|
<span class="message-person-link text-truncate">{{ label }}</span>
|
||||||
|
</template>
|
||||||
|
</RecipientDetails>
|
||||||
</span>
|
</span>
|
||||||
<span class="text-caption text-medium-emphasis ml-2">
|
<span class="text-caption text-medium-emphasis ml-2">
|
||||||
{{ formatDate(timeStamp(message)) }}
|
{{ formatDate(timeStamp(message)) }}
|
||||||
@@ -519,6 +524,19 @@ onBeforeUnmount(() => {
|
|||||||
background-color: rgba(var(--v-theme-primary), 0.14);
|
background-color: rgba(var(--v-theme-primary), 0.14);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-person-link {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1px 4px;
|
||||||
|
margin: -1px -4px;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-person-link:hover {
|
||||||
|
background-color: rgba(var(--v-theme-primary), 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 960px) {
|
@media (max-width: 960px) {
|
||||||
.selection-summary {
|
.selection-summary {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
183
src/components/common/RecipientDetails.vue
Normal file
183
src/components/common/RecipientDetails.vue
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useSnackbar } from '@KTXC'
|
||||||
|
import type { MessageAddressInterface } from '@MailManager/types/message'
|
||||||
|
import { useMailUiStore } from '@/stores/mailUiStore'
|
||||||
|
import { ComposerMode } from '@/types/composer'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
address?: MessageAddressInterface | null
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const mailUiStore = useMailUiStore()
|
||||||
|
const { showSnackbar } = useSnackbar()
|
||||||
|
|
||||||
|
const recipientLabel = computed(() => props.address?.label?.trim() || '')
|
||||||
|
const recipientAddress = computed(() => props.address?.address?.trim() || '')
|
||||||
|
const displayLabel = computed(() => recipientLabel.value || recipientAddress.value || 'Unknown Sender')
|
||||||
|
const formattedAddress = computed(() => {
|
||||||
|
if (recipientLabel.value && recipientAddress.value && recipientLabel.value !== recipientAddress.value) {
|
||||||
|
return `${recipientLabel.value} <${recipientAddress.value}>`
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipientAddress.value || recipientLabel.value
|
||||||
|
})
|
||||||
|
const hasEmail = computed(() => recipientAddress.value.length > 0)
|
||||||
|
|
||||||
|
const showCopyResult = (message: string, color: 'success' | 'error') => {
|
||||||
|
showSnackbar({ message, color })
|
||||||
|
}
|
||||||
|
|
||||||
|
const copy = async (value: string, label: string) => {
|
||||||
|
if (!value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(value)
|
||||||
|
showCopyResult(`${label} copied`, 'success')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Mail][RecipientDetails] Failed to copy text:', error)
|
||||||
|
showCopyResult(`Unable to copy ${label.toLowerCase()}`, 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCompose = () => {
|
||||||
|
if (!props.address) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mailUiStore.openComposer(props.address, ComposerMode.Fresh)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-menu
|
||||||
|
:open-on-hover="true"
|
||||||
|
:open-on-click="false"
|
||||||
|
:open-delay="1000"
|
||||||
|
location="bottom start"
|
||||||
|
transition="slide-y-transition"
|
||||||
|
>
|
||||||
|
<template #activator="{ props: activatorProps }">
|
||||||
|
<span
|
||||||
|
v-bind="activatorProps"
|
||||||
|
class="address-activator"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<slot
|
||||||
|
:label="displayLabel"
|
||||||
|
:name="recipientLabel"
|
||||||
|
:email="recipientAddress"
|
||||||
|
>
|
||||||
|
{{ displayLabel }}
|
||||||
|
</slot>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-card class="address-card" elevation="8" rounded="lg">
|
||||||
|
<div class="address-card-header">
|
||||||
|
<v-avatar size="40" color="primary">
|
||||||
|
<span class="text-white text-subtitle-2">
|
||||||
|
{{ displayLabel[0]?.toUpperCase() || 'U' }}
|
||||||
|
</span>
|
||||||
|
</v-avatar>
|
||||||
|
|
||||||
|
<div class="address-card-meta">
|
||||||
|
<div class="text-subtitle-2 font-weight-medium">{{ displayLabel }}</div>
|
||||||
|
<div v-if="recipientAddress" class="text-body-2 text-medium-emphasis">{{ recipientAddress }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<v-divider class="my-3" />
|
||||||
|
|
||||||
|
<div class="address-card-actions">
|
||||||
|
<v-btn
|
||||||
|
class="address-action-button"
|
||||||
|
size="small"
|
||||||
|
variant="tonal"
|
||||||
|
prepend-icon="mdi-pencil"
|
||||||
|
:disabled="!hasEmail"
|
||||||
|
@click="handleCompose"
|
||||||
|
>
|
||||||
|
Send Email
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
class="address-action-button"
|
||||||
|
size="small"
|
||||||
|
variant="text"
|
||||||
|
:disabled="!hasEmail"
|
||||||
|
@click="copy(recipientAddress, 'Email address')"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-content-copy</v-icon>
|
||||||
|
<v-tooltip activator="parent" location="bottom">Copy Email</v-tooltip>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
class="address-action-button"
|
||||||
|
size="small"
|
||||||
|
variant="text"
|
||||||
|
:disabled="!formattedAddress"
|
||||||
|
@click="copy(formattedAddress, 'Address')"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-card-account-details-outline</v-icon>
|
||||||
|
<v-tooltip activator="parent" location="bottom">Copy Address</v-tooltip>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
v-if="recipientLabel"
|
||||||
|
class="address-action-button"
|
||||||
|
size="small"
|
||||||
|
variant="text"
|
||||||
|
@click="copy(recipientLabel, 'Name')"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-account-outline</v-icon>
|
||||||
|
<v-tooltip activator="parent" location="bottom">Copy Name</v-tooltip>
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.address-activator {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-card {
|
||||||
|
width: min(320px, calc(100vw - 32px));
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-card-meta {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-card-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-card-actions :deep(.address-action-button) {
|
||||||
|
height: 36px;
|
||||||
|
min-height: 36px;
|
||||||
|
width: 36px;
|
||||||
|
min-width: 36px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-card-actions :deep(.address-action-button:first-child) {
|
||||||
|
width: auto;
|
||||||
|
min-width: 0;
|
||||||
|
padding-inline: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ComposerMode } from '@/types/composer'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
mode: 'new' | 'reply' | 'forward'
|
mode: ComposerMode
|
||||||
saveStatus: string
|
saveStatus: string
|
||||||
canSend: boolean
|
canSend: boolean
|
||||||
sending: boolean
|
sending: boolean
|
||||||
@@ -26,7 +28,7 @@ defineEmits<{
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
<v-toolbar-title>
|
<v-toolbar-title>
|
||||||
{{ mode === 'reply' ? 'Reply' : mode === 'forward' ? 'Forward' : 'New Message' }}
|
{{ mode === ComposerMode.Reply ? 'Reply' : mode === ComposerMode.Forward ? 'Forward' : 'New Message' }}
|
||||||
</v-toolbar-title>
|
</v-toolbar-title>
|
||||||
|
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { MessageObject } from '@MailManager/models/message'
|
import { MessageObject } from '@MailManager/models/message'
|
||||||
|
import RecipientDetails from '@/components/common/RecipientDetails.vue'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
message: MessageObject
|
message: MessageObject
|
||||||
@@ -7,6 +8,10 @@ interface Props {
|
|||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const addressKey = (address: { address?: string | null; label?: string | null } | null | undefined, index: number): string => {
|
||||||
|
return `${address?.address || address?.label || 'address'}-${index}`
|
||||||
|
}
|
||||||
|
|
||||||
// Format date for display
|
// Format date for display
|
||||||
const formatDate = (date: Date | string | null | undefined): string => {
|
const formatDate = (date: Date | string | null | undefined): string => {
|
||||||
if (!date) return ''
|
if (!date) return ''
|
||||||
@@ -46,10 +51,14 @@ const formatFileSize = (bytes: number | undefined): string => {
|
|||||||
|
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
<div class="text-body-1 font-weight-medium">
|
<div class="text-body-1 font-weight-medium">
|
||||||
{{ message?.from?.label || message?.from?.address || 'Unknown Sender' }}
|
<RecipientDetails :address="message?.from">
|
||||||
|
<template #default="{ label }">
|
||||||
|
<span class="contact-link">{{ label }}</span>
|
||||||
|
</template>
|
||||||
|
</RecipientDetails>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-caption text-medium-emphasis">
|
<div class="text-caption text-medium-emphasis">
|
||||||
{{ formatDate(message?.date) }}
|
{{ formatDate(message?.received || message?.sent) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -57,12 +66,26 @@ const formatFileSize = (bytes: number | undefined): string => {
|
|||||||
<!-- Recipients -->
|
<!-- Recipients -->
|
||||||
<div v-if="message?.to && message?.to.length > 0" class="text-body-2 mb-1">
|
<div v-if="message?.to && message?.to.length > 0" class="text-body-2 mb-1">
|
||||||
<span class="text-medium-emphasis">To:</span>
|
<span class="text-medium-emphasis">To:</span>
|
||||||
{{ message?.to.map(t => t.label || t.address).join(', ') }}
|
<template v-for="(recipient, index) in message.to" :key="addressKey(recipient, index)">
|
||||||
|
<RecipientDetails :address="recipient">
|
||||||
|
<template #default="{ label }">
|
||||||
|
<span class="contact-link">{{ label }}</span>
|
||||||
|
</template>
|
||||||
|
</RecipientDetails>
|
||||||
|
<span v-if="index < message.to.length - 1">, </span>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="message?.cc && message?.cc.length > 0" class="text-body-2 mb-1">
|
<div v-if="message?.cc && message?.cc.length > 0" class="text-body-2 mb-1">
|
||||||
<span class="text-medium-emphasis">Cc:</span>
|
<span class="text-medium-emphasis">Cc:</span>
|
||||||
{{ message?.cc.map(c => c.label || c.address).join(', ') }}
|
<template v-for="(recipient, index) in message.cc" :key="addressKey(recipient, index)">
|
||||||
|
<RecipientDetails :address="recipient">
|
||||||
|
<template #default="{ label }">
|
||||||
|
<span class="contact-link">{{ label }}</span>
|
||||||
|
</template>
|
||||||
|
</RecipientDetails>
|
||||||
|
<span v-if="index < message.cc.length - 1">, </span>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Attachments -->
|
<!-- Attachments -->
|
||||||
@@ -107,4 +130,16 @@ const formatFileSize = (bytes: number | undefined): string => {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contact-link {
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1px 4px;
|
||||||
|
margin: -1px -4px;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-link:hover {
|
||||||
|
background-color: rgba(var(--v-theme-primary), 0.08);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useCollectionsStore } from '@MailManager/stores/collectionsStore'
|
|||||||
import { useMailStore } from '@/stores/mailStore'
|
import { useMailStore } from '@/stores/mailStore'
|
||||||
import { useMailUiStore } from '@/stores/mailUiStore'
|
import { useMailUiStore } from '@/stores/mailUiStore'
|
||||||
import type { CollectionObject, EntityObject } from '@MailManager/models'
|
import type { CollectionObject, EntityObject } from '@MailManager/models'
|
||||||
|
import { ComposerMode } from '@/types/composer'
|
||||||
import MessageList from '@/components/MessageList.vue'
|
import MessageList from '@/components/MessageList.vue'
|
||||||
import MessageReader from '@/components/MessageReader.vue'
|
import MessageReader from '@/components/MessageReader.vue'
|
||||||
import MessageComposer from '@/components/MessageComposer.vue'
|
import MessageComposer from '@/components/MessageComposer.vue'
|
||||||
@@ -43,16 +44,15 @@ const {
|
|||||||
sidebarVisible,
|
sidebarVisible,
|
||||||
settingsDialogVisible,
|
settingsDialogVisible,
|
||||||
selectedFolder,
|
selectedFolder,
|
||||||
composeMode,
|
composerMode,
|
||||||
composeSource,
|
composerSource,
|
||||||
composeVisible,
|
composerVisible,
|
||||||
selectionList,
|
selectionList,
|
||||||
selectionMode,
|
selectionMode,
|
||||||
moveMessagesDialogVisible,
|
moveMessagesDialogVisible,
|
||||||
moveMessagesDialogService,
|
moveMessagesDialogService,
|
||||||
createFolderDialogVisible,
|
createFolderDialogVisible,
|
||||||
createFolderDialogService,
|
createFolderDialogService,
|
||||||
createFolderDialogParent,
|
|
||||||
createFolderDialogLoading,
|
createFolderDialogLoading,
|
||||||
createFolderDialogError,
|
createFolderDialogError,
|
||||||
renameFolderDialogVisible,
|
renameFolderDialogVisible,
|
||||||
@@ -62,7 +62,6 @@ const {
|
|||||||
renameFolderDialogError,
|
renameFolderDialogError,
|
||||||
moveFolderDialogVisible,
|
moveFolderDialogVisible,
|
||||||
moveFolderDialogService,
|
moveFolderDialogService,
|
||||||
moveFolderDialogSource,
|
|
||||||
deleteFolderDialogVisible,
|
deleteFolderDialogVisible,
|
||||||
deleteFolderDialogService,
|
deleteFolderDialogService,
|
||||||
deleteFolderDialogFolder,
|
deleteFolderDialogFolder,
|
||||||
@@ -149,13 +148,13 @@ const handleMessageOpen = (message: EntityObject) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMessageComposeFresh = () => mailUiStore.openCompose()
|
const handleMessageComposeFresh = () => mailUiStore.openComposer()
|
||||||
|
|
||||||
const handleMessageComposeReply = (message: EntityObject) => mailUiStore.openCompose(message, 'reply')
|
const handleMessageComposeReply = (message: EntityObject) => mailUiStore.openComposer(message, ComposerMode.Reply)
|
||||||
|
|
||||||
const handleMessageComposeForward = (message: EntityObject) => mailUiStore.openCompose(message, 'forward')
|
const handleMessageComposeForward = (message: EntityObject) => mailUiStore.openComposer(message, ComposerMode.Forward)
|
||||||
|
|
||||||
const handleMessageComposeClose = () => mailUiStore.closeCompose()
|
const handleMessageComposeClose = () => mailUiStore.closeComposer()
|
||||||
|
|
||||||
const handleMessageFlag = (message: EntityObject, flag: string, value: boolean) => {
|
const handleMessageFlag = (message: EntityObject, flag: string, value: boolean) => {
|
||||||
mailStore.flagMessages([message.identifier], { [flag]: value })
|
mailStore.flagMessages([message.identifier], { [flag]: value })
|
||||||
@@ -311,9 +310,9 @@ const handleMessageSelectionDelete = () => mailUiStore.deleteSelectedMessages()
|
|||||||
<!-- Reader/Composer panel -->
|
<!-- Reader/Composer panel -->
|
||||||
<div class="mail-reader-panel">
|
<div class="mail-reader-panel">
|
||||||
<MessageComposer
|
<MessageComposer
|
||||||
v-if="composeVisible"
|
v-if="composerVisible"
|
||||||
:mode="composeMode"
|
:mode="composerMode"
|
||||||
:source="composeSource"
|
:source="composerSource"
|
||||||
:folder="selectedFolder"
|
:folder="selectedFolder"
|
||||||
@close="handleMessageComposeClose"
|
@close="handleMessageComposeClose"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import { defineStore } from 'pinia'
|
|||||||
import { useCollectionsStore } from '@MailManager/stores/collectionsStore'
|
import { useCollectionsStore } from '@MailManager/stores/collectionsStore'
|
||||||
import { useMailStore } from '@/stores/mailStore'
|
import { useMailStore } from '@/stores/mailStore'
|
||||||
import { useMailSettingsStore } from '@/stores/mailSettingsStore'
|
import { useMailSettingsStore } from '@/stores/mailSettingsStore'
|
||||||
|
import { ComposerMode } from '@/types/composer'
|
||||||
import type { ServiceIdentifier, EntityIdentifier } from '@MailManager/types/common'
|
import type { ServiceIdentifier, EntityIdentifier } from '@MailManager/types/common'
|
||||||
import { EntityObject, type ServiceObject } from '@MailManager/models'
|
import { EntityObject, type ServiceObject } from '@MailManager/models'
|
||||||
import type { CollectionObject } from '@MailManager/models/collection'
|
import type { CollectionObject } from '@MailManager/models/collection'
|
||||||
|
import type { MessageAddressInterface } from '@MailManager/types/message'
|
||||||
|
|
||||||
export const useMailUiStore = defineStore('mailUiStore', () => {
|
export const useMailUiStore = defineStore('mailUiStore', () => {
|
||||||
const collectionsStore = useCollectionsStore()
|
const collectionsStore = useCollectionsStore()
|
||||||
@@ -16,9 +18,9 @@ export const useMailUiStore = defineStore('mailUiStore', () => {
|
|||||||
const settingsDialogVisible = ref(false)
|
const settingsDialogVisible = ref(false)
|
||||||
const selectedFolder = shallowRef<CollectionObject | null>(null)
|
const selectedFolder = shallowRef<CollectionObject | null>(null)
|
||||||
const selectedMessage = shallowRef<EntityObject | null>(null)
|
const selectedMessage = shallowRef<EntityObject | null>(null)
|
||||||
const composeMode = ref<'new' | 'reply' | 'forward'>('new')
|
const composerMode = ref<ComposerMode>(ComposerMode.Fresh)
|
||||||
const composeSource = shallowRef<EntityObject | null>(null)
|
const composerSource = shallowRef<EntityObject | MessageAddressInterface | null>(null)
|
||||||
const composeVisible = ref(false)
|
const composerVisible = ref(false)
|
||||||
const selectionMode = ref(false)
|
const selectionMode = ref(false)
|
||||||
const selectionList = ref<EntityIdentifier[]>([])
|
const selectionList = ref<EntityIdentifier[]>([])
|
||||||
const moveMessagesDialogVisible = ref(false)
|
const moveMessagesDialogVisible = ref(false)
|
||||||
@@ -96,7 +98,7 @@ export const useMailUiStore = defineStore('mailUiStore', () => {
|
|||||||
() => mailStore.selectedMessage,
|
() => mailStore.selectedMessage,
|
||||||
message => {
|
message => {
|
||||||
if (message) {
|
if (message) {
|
||||||
closeCompose()
|
closeComposer()
|
||||||
}
|
}
|
||||||
|
|
||||||
selectMessage(message)
|
selectMessage(message)
|
||||||
@@ -149,7 +151,7 @@ export const useMailUiStore = defineStore('mailUiStore', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function selectFolder(folder: CollectionObject | null) {
|
async function selectFolder(folder: CollectionObject | null) {
|
||||||
closeCompose()
|
closeComposer()
|
||||||
messageSelectionModeDeactivate()
|
messageSelectionModeDeactivate()
|
||||||
clearMessageReadTimer()
|
clearMessageReadTimer()
|
||||||
selectedMessage.value = null
|
selectedMessage.value = null
|
||||||
@@ -158,7 +160,6 @@ export const useMailUiStore = defineStore('mailUiStore', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function selectMessage(message: EntityObject | null) {
|
async function selectMessage(message: EntityObject | null) {
|
||||||
closeCompose()
|
|
||||||
messageSelectionModeDeactivate()
|
messageSelectionModeDeactivate()
|
||||||
createMessageReadTimer(message)
|
createMessageReadTimer(message)
|
||||||
selectedMessage.value = message
|
selectedMessage.value = message
|
||||||
@@ -450,17 +451,17 @@ export const useMailUiStore = defineStore('mailUiStore', () => {
|
|||||||
selectionList.value = Array.from(new Set(nextIds))
|
selectionList.value = Array.from(new Set(nextIds))
|
||||||
}
|
}
|
||||||
|
|
||||||
function openCompose(source?: EntityObject, mode: 'reply' | 'forward' = 'reply') {
|
function openComposer(source?: EntityObject | MessageAddressInterface, mode: ComposerMode = ComposerMode.Fresh) {
|
||||||
mailStore.selectMessage(null)
|
mailStore.selectMessage(null)
|
||||||
composeSource.value = source ?? null
|
composerSource.value = source ?? null
|
||||||
composeMode.value = mode
|
composerMode.value = mode
|
||||||
composeVisible.value = true
|
composerVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeCompose() {
|
function closeComposer() {
|
||||||
composeMode.value = 'new'
|
composerMode.value = ComposerMode.Fresh
|
||||||
composeSource.value = null
|
composerSource.value = null
|
||||||
composeVisible.value = false
|
composerVisible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function messageSelectionClear() {
|
function messageSelectionClear() {
|
||||||
@@ -575,9 +576,9 @@ export const useMailUiStore = defineStore('mailUiStore', () => {
|
|||||||
settingsDialogVisible,
|
settingsDialogVisible,
|
||||||
selectedFolder,
|
selectedFolder,
|
||||||
selectedMessage,
|
selectedMessage,
|
||||||
composeMode,
|
composerMode,
|
||||||
composeSource,
|
composerSource,
|
||||||
composeVisible,
|
composerVisible,
|
||||||
selectionMode,
|
selectionMode,
|
||||||
selectionList,
|
selectionList,
|
||||||
moveMessagesDialogVisible,
|
moveMessagesDialogVisible,
|
||||||
@@ -610,8 +611,8 @@ export const useMailUiStore = defineStore('mailUiStore', () => {
|
|||||||
settingsClose,
|
settingsClose,
|
||||||
initialize,
|
initialize,
|
||||||
selectFolder,
|
selectFolder,
|
||||||
openCompose,
|
openComposer,
|
||||||
closeCompose,
|
closeComposer,
|
||||||
messageSelectionModeActivate,
|
messageSelectionModeActivate,
|
||||||
messageSelectionModeDeactivate,
|
messageSelectionModeDeactivate,
|
||||||
messageSelectionToggleOne,
|
messageSelectionToggleOne,
|
||||||
|
|||||||
5
src/types/composer.ts
Normal file
5
src/types/composer.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export enum ComposerMode {
|
||||||
|
Fresh,
|
||||||
|
Reply,
|
||||||
|
Forward,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user