487 lines
14 KiB
Vue
487 lines
14 KiB
Vue
<script setup lang="ts">
|
|
import { computed, onMounted, unref } from 'vue'
|
|
import { storeToRefs } from 'pinia'
|
|
import { useDisplay } from 'vuetify'
|
|
import { useModuleStore } from '@KTXC'
|
|
import { useCollectionsStore } from '@MailManager/stores/collectionsStore'
|
|
import { useMailStore } from '@/stores/mailStore'
|
|
import { useMailUiStore } from '@/stores/mailUiStore'
|
|
import type { CollectionObject, EntityObject } from '@MailManager/models'
|
|
import { ComposerMode } from '@/types/composer'
|
|
import MessageList from '@/components/MessageList.vue'
|
|
import MessageReader from '@/components/MessageReader.vue'
|
|
import MessageComposer from '@/components/MessageComposer.vue'
|
|
import CreateFolderDialog from '@/components/CreateFolderDialog.vue'
|
|
import DeleteFolderDialog from '@/components/DeleteFolderDialog.vue'
|
|
import FolderSelectionDialog from '@/components/FolderSelectionDialog.vue'
|
|
import RenameFolderDialog from '@/components/RenameFolderDialog.vue'
|
|
import SettingsDialog from '@/components/settings/SettingsDialog.vue'
|
|
import FolderView from '@/components/FolderView.vue'
|
|
|
|
// Vuetify display for responsive behavior
|
|
const display = useDisplay()
|
|
const isMobile = computed(() => display.mdAndDown.value)
|
|
|
|
// Check if mail manager is available
|
|
const moduleStore = useModuleStore()
|
|
const isManagerAvailable = computed(() => {
|
|
return moduleStore.has('mail_manager') || moduleStore.has('MailManager')
|
|
})
|
|
const collectionsStore = useCollectionsStore()
|
|
|
|
// Mail module store
|
|
const mailStore = useMailStore()
|
|
const mailUiStore = useMailUiStore()
|
|
|
|
// storeToRefs preserves reactivity for state and computed properties
|
|
const {
|
|
loading,
|
|
selectedMessage,
|
|
currentMessages,
|
|
} = storeToRefs(mailStore)
|
|
|
|
const {
|
|
sidebarVisible,
|
|
settingsDialogVisible,
|
|
selectedFolder,
|
|
composerMode,
|
|
composerSource,
|
|
composerVisible,
|
|
selectionList,
|
|
selectionMode,
|
|
moveMessagesDialogVisible,
|
|
moveMessagesDialogService,
|
|
createFolderDialogVisible,
|
|
createFolderDialogService,
|
|
createFolderDialogLoading,
|
|
createFolderDialogError,
|
|
renameFolderDialogVisible,
|
|
renameFolderDialogService,
|
|
renameFolderDialogFolder,
|
|
renameFolderDialogLoading,
|
|
renameFolderDialogError,
|
|
moveFolderDialogVisible,
|
|
moveFolderDialogService,
|
|
deleteFolderDialogVisible,
|
|
deleteFolderDialogService,
|
|
deleteFolderDialogFolder,
|
|
deleteFolderDialogLoading,
|
|
deleteFolderDialogError,
|
|
} = storeToRefs(mailUiStore)
|
|
|
|
// Complex store/composable objects accessed directly (not simple refs)
|
|
const { mailSync, entitiesStore } = mailStore
|
|
|
|
const lastSyncLabel = computed(() => {
|
|
const lastSync = unref(unref(mailSync.lastSync))
|
|
|
|
if (!(lastSync instanceof Date)) return ''
|
|
return `(Last: ${lastSync.toLocaleTimeString()})`
|
|
})
|
|
|
|
// Initialize
|
|
onMounted(async () => {
|
|
if (!isManagerAvailable.value) return
|
|
await mailUiStore.initialize()
|
|
})
|
|
|
|
// Handlers — thin wrappers that delegate to the store
|
|
const {
|
|
validateCreateFolderName,
|
|
validateRenameFolderName,
|
|
} = mailUiStore
|
|
|
|
const sidebarToggle = () => mailUiStore.sidebarToggle()
|
|
|
|
const handleSettingsOpen = () => mailUiStore.settingsOpen()
|
|
|
|
const handleFolderSelect = (folder: CollectionObject) => mailUiStore.selectFolder(folder)
|
|
|
|
const handleFolderCreateConfirm = async (folderName: string) => {
|
|
try {
|
|
const mutatedFolder = await mailUiStore.confirmCreateFolder(folderName)
|
|
|
|
if (mutatedFolder) {
|
|
handleFolderSelect(mutatedFolder)
|
|
}
|
|
} catch (error: unknown) {
|
|
console.error('[MailPage] Failed to create folder:', error)
|
|
}
|
|
}
|
|
|
|
const handleFolderEditConfirm = async (folderName: string) => {
|
|
try {
|
|
const mutatedFolder = await mailUiStore.confirmRenameFolder(folderName)
|
|
|
|
if (mutatedFolder) {
|
|
handleFolderSelect(mutatedFolder)
|
|
}
|
|
} catch (error: unknown) {
|
|
console.error('[MailPage] Failed to rename folder:', error)
|
|
}
|
|
}
|
|
|
|
const handleFolderDeleteConfirm = async () => {
|
|
try {
|
|
await mailUiStore.confirmDeleteFolder()
|
|
} catch (error: unknown) {
|
|
console.error('[MailPage] Failed to delete folder:', error)
|
|
}
|
|
}
|
|
|
|
const handleFolderMoveConfirm = async (targetFolder: CollectionObject) => {
|
|
try {
|
|
await mailUiStore.confirmMoveFolder(targetFolder)
|
|
} catch (error: unknown) {
|
|
console.error('[MailPage] Failed to move folder:', error)
|
|
}
|
|
}
|
|
|
|
const handleFolderMoveCancel = () => mailUiStore.closeMoveFolderDialog()
|
|
|
|
|
|
const handleMessageOpen = (message: EntityObject) => {
|
|
mailStore.selectMessage(message)
|
|
|
|
if (isMobile.value) {
|
|
mailUiStore.sidebarHide()
|
|
}
|
|
}
|
|
|
|
const handleMessageComposeFresh = () => mailUiStore.openComposer()
|
|
|
|
const handleMessageComposeReply = (message: EntityObject) => mailUiStore.openComposer(message, ComposerMode.Reply)
|
|
|
|
const handleMessageComposeForward = (message: EntityObject) => mailUiStore.openComposer(message, ComposerMode.Forward)
|
|
|
|
const handleMessageComposeClose = () => mailUiStore.closeComposer()
|
|
|
|
const handleMessageFlag = (message: EntityObject, flag: string, value: boolean) => {
|
|
mailStore.flagMessages([message.identifier], { [flag]: value })
|
|
}
|
|
|
|
const handleMessageDelete = (message: EntityObject) => {
|
|
mailStore.deleteMessages([message.identifier])
|
|
}
|
|
|
|
const handleMessageMove = (message: EntityObject) => mailUiStore.openMoveMessagesDialog(message)
|
|
|
|
const handleMessageMoveConfirm = async (target: CollectionObject) => { await mailUiStore.confirmMoveMessages(target) }
|
|
|
|
const handleMessageMoveCancel = () => mailUiStore.closeMoveMessagesDialog()
|
|
|
|
const handleMessageSelectionMode = (message: EntityObject) => mailUiStore.messageSelectionModeActivate(message)
|
|
|
|
const handleMessageSelectionToggleOne = (message: EntityObject) => mailUiStore.messageSelectionToggleOne(message)
|
|
|
|
const handleMessageSelectionToggleAll = (value: boolean) => {
|
|
mailUiStore.messageSelectionToggleAll(value)
|
|
}
|
|
|
|
const handleMessageSelectionClear = () => mailUiStore.messageSelectionModeDeactivate()
|
|
|
|
const handleMessageSelectionMove = () => mailUiStore.openMoveMessagesDialog()
|
|
|
|
const handleMessageSelectionFlag = (flag: string, value: boolean) => mailUiStore.flagSelectedMessages(flag, value)
|
|
|
|
const handleMessageSelectionDelete = () => mailUiStore.deleteSelectedMessages()
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<!-- Manager Unavailable -->
|
|
<div v-if="!isManagerAvailable" class="mail-unavailable">
|
|
<v-alert
|
|
type="warning"
|
|
variant="outlined"
|
|
class="mail-unavailable-alert"
|
|
>
|
|
<v-icon size="64" color="warning" class="mb-6">mdi-email-off-outline</v-icon>
|
|
<h2 class="text-h5 font-weight-bold mb-4">Mail Manager Not Available</h2>
|
|
<p>
|
|
The Mail Manager module is not installed or enabled.
|
|
This module requires the <strong>mail_manager</strong> module to function properly.
|
|
</p>
|
|
<p class="mb-0 mt-2">
|
|
Please contact your system administrator to install and enable the
|
|
<code>mail_manager</code> module.
|
|
</p>
|
|
</v-alert>
|
|
</div>
|
|
|
|
<!-- Normal UI -->
|
|
<div v-else class="mail-container">
|
|
<!-- Top toolbar -->
|
|
<v-app-bar class="mail-toolbar" elevation="0" density="compact">
|
|
<v-app-bar-nav-icon
|
|
v-if="isMobile"
|
|
@click="sidebarToggle"
|
|
/>
|
|
|
|
<v-app-bar-title>Mail</v-app-bar-title>
|
|
|
|
<v-spacer />
|
|
|
|
<v-btn
|
|
icon="mdi-pencil"
|
|
@click="handleMessageComposeFresh()"
|
|
color="primary"
|
|
variant="text"
|
|
>
|
|
<v-icon>mdi-pencil</v-icon>
|
|
<v-tooltip activator="parent" location="bottom">Compose</v-tooltip>
|
|
</v-btn>
|
|
|
|
<v-btn
|
|
icon="mdi-refresh"
|
|
@click="mailSync.sync()"
|
|
:loading="mailSync.isRunning && entitiesStore.transceiving"
|
|
variant="text"
|
|
>
|
|
<v-icon>mdi-refresh</v-icon>
|
|
<v-tooltip activator="parent" location="bottom">
|
|
Refresh {{ lastSyncLabel }}
|
|
</v-tooltip>
|
|
</v-btn>
|
|
|
|
<v-icon
|
|
v-if="mailSync.isRunning"
|
|
color="success"
|
|
size="small"
|
|
class="ml-2"
|
|
>
|
|
mdi-sync
|
|
</v-icon>
|
|
</v-app-bar>
|
|
|
|
<!-- Main content area -->
|
|
<div class="mail-content">
|
|
<!-- Folder tree sidebar -->
|
|
<v-navigation-drawer
|
|
v-model="sidebarVisible"
|
|
:permanent="!isMobile"
|
|
:temporary="isMobile"
|
|
width="280"
|
|
class="mail-sidebar"
|
|
>
|
|
|
|
<FolderView
|
|
:selected-folder="selectedFolder"
|
|
@select="handleFolderSelect"
|
|
/>
|
|
|
|
<template #append>
|
|
<div class="pa-2">
|
|
<v-btn
|
|
block
|
|
variant="text"
|
|
prepend-icon="mdi-cog"
|
|
@click="handleSettingsOpen"
|
|
>
|
|
Settings
|
|
</v-btn>
|
|
</div>
|
|
</template>
|
|
</v-navigation-drawer>
|
|
|
|
<!-- Main area with message list and reader -->
|
|
<div class="mail-main">
|
|
<div class="mail-wrapper">
|
|
<!-- Message list panel -->
|
|
<div class="mail-list-panel">
|
|
<MessageList
|
|
:messages="currentMessages"
|
|
:selected-collection="selectedFolder"
|
|
:selected-message="selectedMessage"
|
|
:selection-list="selectionList"
|
|
:selection-mode="selectionMode"
|
|
:loading="loading"
|
|
@open="handleMessageOpen"
|
|
@selection-mode="handleMessageSelectionMode"
|
|
@selection-toggle-one="handleMessageSelectionToggleOne"
|
|
@selection-toggle-all="handleMessageSelectionToggleAll"
|
|
@selection-clear="handleMessageSelectionClear"
|
|
@selection-flag="handleMessageSelectionFlag"
|
|
@selection-move="handleMessageSelectionMove"
|
|
@selection-delete="handleMessageSelectionDelete"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Reader/Composer panel -->
|
|
<div class="mail-reader-panel">
|
|
<MessageComposer
|
|
v-if="composerVisible"
|
|
:mode="composerMode"
|
|
:source="composerSource"
|
|
:folder="selectedFolder"
|
|
@close="handleMessageComposeClose"
|
|
/>
|
|
|
|
<MessageReader
|
|
v-else
|
|
:entity="selectedMessage"
|
|
@compose="handleMessageComposeFresh"
|
|
@reply="handleMessageComposeReply"
|
|
@forward="handleMessageComposeForward"
|
|
@move="handleMessageMove"
|
|
@delete="handleMessageDelete"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Settings Dialog -->
|
|
<SettingsDialog v-model="settingsDialogVisible" />
|
|
|
|
<FolderSelectionDialog
|
|
v-if="moveMessagesDialogService"
|
|
v-model="moveMessagesDialogVisible"
|
|
:service="moveMessagesDialogService"
|
|
:loading="loading"
|
|
title="Move Messages To"
|
|
confirm-text="Move"
|
|
empty-text="No other folders are available in this account."
|
|
@select="handleMessageMoveConfirm"
|
|
@cancel="handleMessageMoveCancel"
|
|
/>
|
|
|
|
<FolderSelectionDialog
|
|
v-if="moveFolderDialogService"
|
|
v-model="moveFolderDialogVisible"
|
|
:service="moveFolderDialogService"
|
|
:loading="collectionsStore.transceiving"
|
|
title="Move Folder To"
|
|
confirm-text="Move"
|
|
empty-text="No other folders are available in this account."
|
|
:disabled-folder-keys="mailUiStore.moveFolderDialogInvalidFolderKeys"
|
|
@select="handleFolderMoveConfirm"
|
|
@cancel="handleFolderMoveCancel"
|
|
/>
|
|
|
|
<CreateFolderDialog
|
|
v-if="createFolderDialogService"
|
|
v-model="createFolderDialogVisible"
|
|
:service="createFolderDialogService"
|
|
:parent-folder-label="mailUiStore.createFolderDialogParentLabel"
|
|
:validate-name="validateCreateFolderName"
|
|
:loading="createFolderDialogLoading"
|
|
:error-message="createFolderDialogError"
|
|
@confirm="handleFolderCreateConfirm"
|
|
/>
|
|
|
|
<RenameFolderDialog
|
|
v-if="renameFolderDialogService && renameFolderDialogFolder"
|
|
v-model="renameFolderDialogVisible"
|
|
:service="renameFolderDialogService"
|
|
:folder="renameFolderDialogFolder"
|
|
:parent-folder-label="mailUiStore.renameFolderDialogParentLabel"
|
|
:validate-name="validateRenameFolderName"
|
|
:loading="renameFolderDialogLoading"
|
|
:error-message="renameFolderDialogError"
|
|
@confirm="handleFolderEditConfirm"
|
|
/>
|
|
|
|
<DeleteFolderDialog
|
|
v-if="deleteFolderDialogService && deleteFolderDialogFolder"
|
|
v-model="deleteFolderDialogVisible"
|
|
:service="deleteFolderDialogService"
|
|
:folder="deleteFolderDialogFolder"
|
|
:loading="deleteFolderDialogLoading"
|
|
:error-message="deleteFolderDialogError"
|
|
@confirm="handleFolderDeleteConfirm"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped lang="scss">
|
|
.mail-unavailable {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 100vh;
|
|
padding: 48px;
|
|
text-align: center;
|
|
width: 100%;
|
|
}
|
|
|
|
.mail-unavailable-alert {
|
|
width: 100%;
|
|
text-align: left;
|
|
}
|
|
|
|
.mail-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100vh;
|
|
isolation: isolate;
|
|
}
|
|
|
|
.mail-toolbar {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.mail-content {
|
|
display: flex;
|
|
flex: 1;
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
.mail-sidebar {
|
|
border-right: 1px solid rgb(var(--v-border-color));
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.mail-main {
|
|
flex: 1;
|
|
display: flex;
|
|
overflow: hidden;
|
|
min-width: 0;
|
|
}
|
|
|
|
.mail-wrapper {
|
|
flex: 1;
|
|
display: flex;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.mail-list-panel {
|
|
width: 320px;
|
|
min-width: 280px;
|
|
max-width: 450px;
|
|
border-right: 1px solid rgb(var(--v-border-color));
|
|
overflow-y: auto;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.mail-reader-panel {
|
|
flex: 1;
|
|
overflow: hidden;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
/* Responsive adjustments */
|
|
@media (max-width: 960px) {
|
|
.mail-wrapper {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.mail-list-panel {
|
|
width: 100%;
|
|
max-width: 100%;
|
|
border-right: none;
|
|
border-bottom: 1px solid rgb(var(--v-border-color));
|
|
max-height: 50%;
|
|
}
|
|
|
|
.mail-reader-panel {
|
|
width: 100%;
|
|
}
|
|
}
|
|
</style>
|