feat: implement patch and settings store #42
@@ -1,16 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
import { useServicesStore } from '@MailManager/stores/servicesStore'
|
import { useServicesStore } from '@MailManager/stores/servicesStore'
|
||||||
import { useMailStore } from '@/stores/mailStore'
|
import { useMailStore } from '@/stores/mailStore'
|
||||||
|
import { useMailSettingsStore } from '@/stores/mailSettingsStore'
|
||||||
import { useMailUiStore } from '@/stores/mailUiStore'
|
import { useMailUiStore } from '@/stores/mailUiStore'
|
||||||
import { useUser } from '@KTXC'
|
|
||||||
import FolderTreeView from './FolderTreeView.vue'
|
import FolderTreeView from './FolderTreeView.vue'
|
||||||
import FolderPageView from './FolderPageView.vue'
|
import FolderPageView from './FolderPageView.vue'
|
||||||
import type { CollectionObject } from '@MailManager/models/collection'
|
import type { CollectionObject } from '@MailManager/models/collection'
|
||||||
import type { ServiceObject } from '@MailManager/models'
|
import type { ServiceObject } from '@MailManager/models'
|
||||||
|
|
||||||
type FolderViewMode = 'tree' | 'page'
|
|
||||||
|
|
||||||
interface ServiceGroup {
|
interface ServiceGroup {
|
||||||
service: ServiceObject
|
service: ServiceObject
|
||||||
loading: boolean
|
loading: boolean
|
||||||
@@ -31,12 +30,8 @@ const emit = defineEmits<{
|
|||||||
const servicesStore = useServicesStore()
|
const servicesStore = useServicesStore()
|
||||||
const mailStore = useMailStore()
|
const mailStore = useMailStore()
|
||||||
const mailUiStore = useMailUiStore()
|
const mailUiStore = useMailUiStore()
|
||||||
const { settings } = useUser()
|
const mailSettingsStore = useMailSettingsStore()
|
||||||
|
const { folderViewMode } = storeToRefs(mailSettingsStore)
|
||||||
// Computed
|
|
||||||
const folderViewMode = computed(() => {
|
|
||||||
return (settings.value.get('mail.folderViewMode') as FolderViewMode) || 'tree'
|
|
||||||
})
|
|
||||||
|
|
||||||
const serviceGroups = computed(() => {
|
const serviceGroups = computed(() => {
|
||||||
const groups: ServiceGroup[] = []
|
const groups: ServiceGroup[] = []
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const emit = defineEmits<{
|
|||||||
selectionClear: []
|
selectionClear: []
|
||||||
selectionMove: []
|
selectionMove: []
|
||||||
selectionDelete: []
|
selectionDelete: []
|
||||||
|
selectionFlag: [flag: string, value: boolean]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const longPressTimer = ref<number | null>(null)
|
const longPressTimer = ref<number | null>(null)
|
||||||
@@ -202,6 +203,13 @@ const clearLongPressTimer = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleFlag = (flag: string, value: boolean) => {
|
||||||
|
if (props.selectionMode && selectionCount.value > 0) {
|
||||||
|
emit('selectionFlag', flag, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
clearLongPressTimer()
|
clearLongPressTimer()
|
||||||
})
|
})
|
||||||
@@ -260,6 +268,16 @@ onBeforeUnmount(() => {
|
|||||||
<v-icon>mdi-delete-outline</v-icon>
|
<v-icon>mdi-delete-outline</v-icon>
|
||||||
<v-tooltip activator="parent" location="bottom">Delete Selected</v-tooltip>
|
<v-tooltip activator="parent" location="bottom">Delete Selected</v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
size="small"
|
||||||
|
icon="mdi-read"
|
||||||
|
variant="text"
|
||||||
|
:disabled="selectionCount === 0"
|
||||||
|
@click="handleFlag('read', true)"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-read</v-icon>
|
||||||
|
<v-tooltip activator="parent" location="bottom">Mark as Read</v-tooltip>
|
||||||
|
</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
size="small"
|
size="small"
|
||||||
icon="mdi-close"
|
icon="mdi-close"
|
||||||
|
|||||||
44
src/components/settings/BehaviorSettings.vue
Normal file
44
src/components/settings/BehaviorSettings.vue
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { messageReadDelayOptions, useMailSettingsStore } from '@/stores/mailSettingsStore'
|
||||||
|
|
||||||
|
const mailSettingsStore = useMailSettingsStore()
|
||||||
|
const { messageReadEnabled, messageReadDelay } = storeToRefs(mailSettingsStore)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="pa-4">
|
||||||
|
<h3 class="text-h6 mb-4">Behaviours</h3>
|
||||||
|
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-list-item-title>Mark messages as read automatically</v-list-item-title>
|
||||||
|
<v-list-item-subtitle>
|
||||||
|
Mark a message as read after it stays open for the configured delay
|
||||||
|
</v-list-item-subtitle>
|
||||||
|
<template #append>
|
||||||
|
<v-switch v-model="messageReadEnabled" color="primary" hide-details />
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
|
||||||
|
<v-list-item>
|
||||||
|
<v-list-item-title>Read delay</v-list-item-title>
|
||||||
|
<v-list-item-subtitle>
|
||||||
|
Choose how long a message must stay open before it is marked as read
|
||||||
|
</v-list-item-subtitle>
|
||||||
|
<template #append>
|
||||||
|
<v-select
|
||||||
|
v-model="messageReadDelay"
|
||||||
|
:items="messageReadDelayOptions"
|
||||||
|
item-title="title"
|
||||||
|
item-value="value"
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
:disabled="!messageReadEnabled"
|
||||||
|
style="width: 180px"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -1,67 +1,23 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useUser } from '@KTXC'
|
import { folderViewModeOptions, useMailSettingsStore } from '@/stores/mailSettingsStore'
|
||||||
|
|
||||||
type FolderViewMode = 'tree' | 'page'
|
const mailSettingsStore = useMailSettingsStore()
|
||||||
|
const { folderViewMode } = storeToRefs(mailSettingsStore)
|
||||||
const { settings, setSetting } = useUser()
|
|
||||||
|
|
||||||
const theme = ref('Auto')
|
|
||||||
const showPreview = ref(true)
|
|
||||||
const compactMode = ref(false)
|
|
||||||
|
|
||||||
const folderViewMode = computed({
|
|
||||||
get: () => {
|
|
||||||
return (settings.value.get('mail.folderViewMode') as FolderViewMode) || 'tree'
|
|
||||||
},
|
|
||||||
set: (value: FolderViewMode) => setSetting('mail.folderViewMode', value)
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="pa-4">
|
<div class="pa-4">
|
||||||
<h3 class="text-h6 mb-4">Display Settings</h3>
|
<h3 class="text-h6 mb-4">Display Settings</h3>
|
||||||
|
|
||||||
<v-list>
|
<v-list>
|
||||||
<v-list-item>
|
|
||||||
<v-list-item-title>Theme</v-list-item-title>
|
|
||||||
<v-list-item-subtitle>Choose your preferred theme</v-list-item-subtitle>
|
|
||||||
<template #append>
|
|
||||||
<v-select
|
|
||||||
v-model="theme"
|
|
||||||
:items="['Light', 'Dark', 'Auto']"
|
|
||||||
density="compact"
|
|
||||||
variant="outlined"
|
|
||||||
style="width: 150px"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</v-list-item>
|
|
||||||
|
|
||||||
<v-list-item>
|
|
||||||
<v-list-item-title>Message preview</v-list-item-title>
|
|
||||||
<v-list-item-subtitle>Show message preview in list</v-list-item-subtitle>
|
|
||||||
<template #append>
|
|
||||||
<v-switch v-model="showPreview" color="primary" hide-details />
|
|
||||||
</template>
|
|
||||||
</v-list-item>
|
|
||||||
|
|
||||||
<v-list-item>
|
|
||||||
<v-list-item-title>Compact mode</v-list-item-title>
|
|
||||||
<v-list-item-subtitle>Use compact message list layout</v-list-item-subtitle>
|
|
||||||
<template #append>
|
|
||||||
<v-switch v-model="compactMode" color="primary" hide-details />
|
|
||||||
</template>
|
|
||||||
</v-list-item>
|
|
||||||
|
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-list-item-title>Folder navigation style</v-list-item-title>
|
<v-list-item-title>Folder navigation style</v-list-item-title>
|
||||||
<v-list-item-subtitle>Choose how folders are displayed</v-list-item-subtitle>
|
<v-list-item-subtitle>Choose how folders are displayed</v-list-item-subtitle>
|
||||||
<template #append>
|
<template #append>
|
||||||
<v-select
|
<v-select
|
||||||
v-model="folderViewMode"
|
v-model="folderViewMode"
|
||||||
:items="[
|
:items="folderViewModeOptions"
|
||||||
{ value: 'tree', title: 'Tree' },
|
|
||||||
{ value: 'page', title: 'Page' }
|
|
||||||
]"
|
|
||||||
item-value="value"
|
item-value="value"
|
||||||
item-title="title"
|
item-title="title"
|
||||||
density="compact"
|
density="compact"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import DisplaySettings from './DisplaySettings.vue'
|
import DisplaySettings from './DisplaySettings.vue'
|
||||||
import AccountsSettings from './AccountsSettings.vue'
|
import AccountsSettings from './AccountsSettings.vue'
|
||||||
|
import BehaviorSettings from './BehaviorSettings.vue'
|
||||||
import SecuritySettings from './SecuritySettings.vue'
|
import SecuritySettings from './SecuritySettings.vue'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -51,6 +52,10 @@ const handleClose = () => {
|
|||||||
<v-icon start>mdi-palette</v-icon>
|
<v-icon start>mdi-palette</v-icon>
|
||||||
Display
|
Display
|
||||||
</v-tab>
|
</v-tab>
|
||||||
|
<v-tab value="behaviour">
|
||||||
|
<v-icon start>mdi-timer-cog-outline</v-icon>
|
||||||
|
Behaviours
|
||||||
|
</v-tab>
|
||||||
<v-tab value="security">
|
<v-tab value="security">
|
||||||
<v-icon start>mdi-shield-account</v-icon>
|
<v-icon start>mdi-shield-account</v-icon>
|
||||||
Security
|
Security
|
||||||
@@ -68,6 +73,10 @@ const handleClose = () => {
|
|||||||
<DisplaySettings />
|
<DisplaySettings />
|
||||||
</v-window-item>
|
</v-window-item>
|
||||||
|
|
||||||
|
<v-window-item value="behaviour">
|
||||||
|
<BehaviorSettings />
|
||||||
|
</v-window-item>
|
||||||
|
|
||||||
<v-window-item value="security">
|
<v-window-item value="security">
|
||||||
<SecuritySettings />
|
<SecuritySettings />
|
||||||
</v-window-item>
|
</v-window-item>
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ const mailUiStore = useMailUiStore()
|
|||||||
// storeToRefs preserves reactivity for state and computed properties
|
// storeToRefs preserves reactivity for state and computed properties
|
||||||
const {
|
const {
|
||||||
loading,
|
loading,
|
||||||
selectedFolder,
|
|
||||||
selectedMessage,
|
selectedMessage,
|
||||||
currentMessages,
|
currentMessages,
|
||||||
} = storeToRefs(mailStore)
|
} = storeToRefs(mailStore)
|
||||||
@@ -43,6 +42,7 @@ const {
|
|||||||
const {
|
const {
|
||||||
sidebarVisible,
|
sidebarVisible,
|
||||||
settingsDialogVisible,
|
settingsDialogVisible,
|
||||||
|
selectedFolder,
|
||||||
composeMode,
|
composeMode,
|
||||||
composeSource,
|
composeSource,
|
||||||
composeVisible,
|
composeVisible,
|
||||||
@@ -83,7 +83,7 @@ const lastSyncLabel = computed(() => {
|
|||||||
// Initialize
|
// Initialize
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (!isManagerAvailable.value) return
|
if (!isManagerAvailable.value) return
|
||||||
await mailStore.initialize()
|
await mailUiStore.initialize()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Handlers — thin wrappers that delegate to the store
|
// Handlers — thin wrappers that delegate to the store
|
||||||
@@ -92,7 +92,11 @@ const {
|
|||||||
validateRenameFolderName,
|
validateRenameFolderName,
|
||||||
} = mailUiStore
|
} = mailUiStore
|
||||||
|
|
||||||
const handleFolderSelect = (folder: CollectionObject) => mailStore.selectFolder(folder)
|
const sidebarToggle = () => mailUiStore.sidebarToggle()
|
||||||
|
|
||||||
|
const handleSettingsOpen = () => mailUiStore.settingsOpen()
|
||||||
|
|
||||||
|
const handleFolderSelect = (folder: CollectionObject) => mailUiStore.selectFolder(folder)
|
||||||
|
|
||||||
const handleFolderCreateConfirm = async (folderName: string) => {
|
const handleFolderCreateConfirm = async (folderName: string) => {
|
||||||
try {
|
try {
|
||||||
@@ -141,7 +145,7 @@ const handleMessageOpen = (message: EntityObject) => {
|
|||||||
mailStore.selectMessage(message)
|
mailStore.selectMessage(message)
|
||||||
|
|
||||||
if (isMobile.value) {
|
if (isMobile.value) {
|
||||||
mailUiStore.closeSidebar()
|
mailUiStore.sidebarHide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +157,10 @@ const handleMessageComposeForward = (message: EntityObject) => mailUiStore.openC
|
|||||||
|
|
||||||
const handleMessageComposeClose = () => mailUiStore.closeCompose()
|
const handleMessageComposeClose = () => mailUiStore.closeCompose()
|
||||||
|
|
||||||
|
const handleMessageFlag = (message: EntityObject, flag: string, value: boolean) => {
|
||||||
|
mailStore.flagMessages([message.identifier], { [flag]: value })
|
||||||
|
}
|
||||||
|
|
||||||
const handleMessageDelete = (message: EntityObject) => {
|
const handleMessageDelete = (message: EntityObject) => {
|
||||||
mailStore.deleteMessages([message.identifier])
|
mailStore.deleteMessages([message.identifier])
|
||||||
}
|
}
|
||||||
@@ -175,12 +183,10 @@ const handleMessageSelectionClear = () => mailUiStore.messageSelectionModeDeacti
|
|||||||
|
|
||||||
const handleMessageSelectionMove = () => mailUiStore.openMoveMessagesDialog()
|
const handleMessageSelectionMove = () => mailUiStore.openMoveMessagesDialog()
|
||||||
|
|
||||||
|
const handleMessageSelectionFlag = (flag: string, value: boolean) => mailUiStore.flagSelectedMessages(flag, value)
|
||||||
|
|
||||||
const handleMessageSelectionDelete = () => mailUiStore.deleteSelectedMessages()
|
const handleMessageSelectionDelete = () => mailUiStore.deleteSelectedMessages()
|
||||||
|
|
||||||
const toggleSidebar = () => mailUiStore.toggleSidebar()
|
|
||||||
|
|
||||||
const handleSettingsOpen = () => mailUiStore.openSettings()
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -210,7 +216,7 @@ const handleSettingsOpen = () => mailUiStore.openSettings()
|
|||||||
<v-app-bar class="mail-toolbar" elevation="0" density="compact">
|
<v-app-bar class="mail-toolbar" elevation="0" density="compact">
|
||||||
<v-app-bar-nav-icon
|
<v-app-bar-nav-icon
|
||||||
v-if="isMobile"
|
v-if="isMobile"
|
||||||
@click="toggleSidebar"
|
@click="sidebarToggle"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<v-app-bar-title>Mail</v-app-bar-title>
|
<v-app-bar-title>Mail</v-app-bar-title>
|
||||||
@@ -296,6 +302,7 @@ const handleSettingsOpen = () => mailUiStore.openSettings()
|
|||||||
@selection-toggle-one="handleMessageSelectionToggleOne"
|
@selection-toggle-one="handleMessageSelectionToggleOne"
|
||||||
@selection-toggle-all="handleMessageSelectionToggleAll"
|
@selection-toggle-all="handleMessageSelectionToggleAll"
|
||||||
@selection-clear="handleMessageSelectionClear"
|
@selection-clear="handleMessageSelectionClear"
|
||||||
|
@selection-flag="handleMessageSelectionFlag"
|
||||||
@selection-move="handleMessageSelectionMove"
|
@selection-move="handleMessageSelectionMove"
|
||||||
@selection-delete="handleMessageSelectionDelete"
|
@selection-delete="handleMessageSelectionDelete"
|
||||||
/>
|
/>
|
||||||
|
|||||||
74
src/stores/mailSettingsStore.ts
Normal file
74
src/stores/mailSettingsStore.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { computed } from 'vue'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { useUserStore } from '@KTXC'
|
||||||
|
|
||||||
|
const MESSAGE_READ_ENABLED_KEY = 'mail.behaviour.messageReadEnabled'
|
||||||
|
const MESSAGE_READ_DELAY_KEY = 'mail.behaviour.messageReadDelay'
|
||||||
|
const FOLDER_VIEW_MODE_KEY = 'mail.folderViewMode'
|
||||||
|
|
||||||
|
const DEFAULT_MESSAGE_READ_ENABLED = false
|
||||||
|
const DEFAULT_MESSAGE_READ_DELAY = 5
|
||||||
|
const DEFAULT_FOLDER_VIEW_MODE = 'tree'
|
||||||
|
|
||||||
|
export type FolderViewMode = 'tree' | 'page'
|
||||||
|
|
||||||
|
export const messageReadDelayOptions = [
|
||||||
|
{ value: 2, title: '2 seconds' },
|
||||||
|
{ value: 5, title: '5 seconds' },
|
||||||
|
{ value: 10, title: '10 seconds' },
|
||||||
|
{ value: 30, title: '30 seconds' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export const folderViewModeOptions = [
|
||||||
|
{ value: 'tree', title: 'Tree' },
|
||||||
|
{ value: 'page', title: 'Page' },
|
||||||
|
]
|
||||||
|
|
||||||
|
function normalizeBoolean(value: unknown, fallback: boolean): boolean {
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePositiveNumber(value: unknown, fallback: number): number {
|
||||||
|
const normalized = Number(value)
|
||||||
|
|
||||||
|
return Number.isFinite(normalized) && normalized > 0 ? normalized : fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeFolderViewMode(value: unknown, fallback: FolderViewMode): FolderViewMode {
|
||||||
|
return value === 'tree' || value === 'page' ? value : fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useMailSettingsStore = defineStore('mailSettingsStore', () => {
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
const messageReadEnabled = computed({
|
||||||
|
get: () => normalizeBoolean(userStore.getSetting(MESSAGE_READ_ENABLED_KEY), DEFAULT_MESSAGE_READ_ENABLED),
|
||||||
|
set: (value: boolean) => userStore.setSetting(MESSAGE_READ_ENABLED_KEY, value),
|
||||||
|
})
|
||||||
|
|
||||||
|
const messageReadDelay = computed({
|
||||||
|
get: () => normalizePositiveNumber(userStore.getSetting(MESSAGE_READ_DELAY_KEY), DEFAULT_MESSAGE_READ_DELAY),
|
||||||
|
set: (value: number) => userStore.setSetting(
|
||||||
|
MESSAGE_READ_DELAY_KEY,
|
||||||
|
normalizePositiveNumber(value, DEFAULT_MESSAGE_READ_DELAY),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
const folderViewMode = computed({
|
||||||
|
get: () => normalizeFolderViewMode(userStore.getSetting(FOLDER_VIEW_MODE_KEY), DEFAULT_FOLDER_VIEW_MODE),
|
||||||
|
set: (value: FolderViewMode) => userStore.setSetting(
|
||||||
|
FOLDER_VIEW_MODE_KEY,
|
||||||
|
normalizeFolderViewMode(value, DEFAULT_FOLDER_VIEW_MODE),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
folderViewMode,
|
||||||
|
messageReadEnabled,
|
||||||
|
messageReadDelay,
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -78,9 +78,7 @@ export const useMailStore = defineStore('mailStore', () => {
|
|||||||
await servicesStore.list()
|
await servicesStore.list()
|
||||||
|
|
||||||
const services = [...servicesStore.servicesEnabled]
|
const services = [...servicesStore.servicesEnabled]
|
||||||
services.forEach(service => {
|
await Promise.all(services.map(service => loadFoldersForService(service)))
|
||||||
void loadFoldersForService(service,{ selectInbox: true })
|
|
||||||
})
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Mail][Operations] Failed to initialize:', error)
|
console.error('[Mail][Operations] Failed to initialize:', error)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -88,10 +86,7 @@ export const useMailStore = defineStore('mailStore', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadFoldersForService(
|
async function loadFoldersForService(service: ServiceObject) {
|
||||||
service: ServiceObject,
|
|
||||||
options: { selectInbox?: boolean } = {},
|
|
||||||
) {
|
|
||||||
|
|
||||||
if (service.identifier === null) {
|
if (service.identifier === null) {
|
||||||
return
|
return
|
||||||
@@ -102,24 +97,10 @@ export const useMailStore = defineStore('mailStore', () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// retrieve folders for service
|
// retrieve folders for service
|
||||||
const collections = await collectionsStore.collectionsForService(service.provider, service.identifier, true)
|
await collectionsStore.collectionsForService(service.provider, service.identifier, true)
|
||||||
|
|
||||||
_setServiceFolderLoaded(service.provider, service.identifier, true)
|
_setServiceFolderLoaded(service.provider, service.identifier, true)
|
||||||
|
|
||||||
if (options.selectInbox && !selectedFolder.value) {
|
|
||||||
const inbox = Object.values(collections).find(
|
|
||||||
folder =>
|
|
||||||
folder.provider === service.provider &&
|
|
||||||
String(folder.service) === String(service.identifier) &&
|
|
||||||
(folder.properties.role === 'inbox' ||
|
|
||||||
String(folder.identifier).toLowerCase() === 'inbox'),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (inbox) {
|
|
||||||
await selectFolder(inbox)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateSyncSources()
|
_updateSyncSources()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : 'Failed to load folders'
|
const message = error instanceof Error ? error.message : 'Failed to load folders'
|
||||||
@@ -323,41 +304,25 @@ export const useMailStore = defineStore('mailStore', () => {
|
|||||||
return service
|
return service
|
||||||
}
|
}
|
||||||
|
|
||||||
async function selectFolder(folder: CollectionObject) {
|
async function selectFolder(folder: CollectionObject | null) {
|
||||||
selectedFolder.value = folder
|
selectedFolder.value = folder
|
||||||
selectedMessage.value = null
|
selectedMessage.value = null
|
||||||
|
|
||||||
|
if (folder) {
|
||||||
try {
|
try {
|
||||||
await entitiesStore.list([folder.identifier])
|
await entitiesStore.list([folder.identifier])
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Mail][Operations] Failed to load messages:', error)
|
console.error('[Mail][Operations] Failed to load messages:', error)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_updateSyncSources()
|
_updateSyncSources()
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearSelectedFolder() {
|
function selectMessage(entity: EntityObject | null) {
|
||||||
selectedFolder.value = null
|
|
||||||
selectedMessage.value = null
|
|
||||||
|
|
||||||
_updateSyncSources()
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectMessage(entity: EntityObject) {
|
|
||||||
selectedMessage.value = entity
|
selectedMessage.value = entity
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearSelectedMessage() {
|
|
||||||
selectedMessage.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
async function reloadSelectedFolder() {
|
|
||||||
// Reload the current folder so the sent message appears in Sent
|
|
||||||
if (selectedFolder.value) {
|
|
||||||
await selectFolder(selectedFolder.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveComposerDraft(folder: CollectionObject, message: ComposerMessageInput) {
|
async function saveComposerDraft(folder: CollectionObject, message: ComposerMessageInput) {
|
||||||
composerSaving.value = true
|
composerSaving.value = true
|
||||||
|
|
||||||
@@ -380,6 +345,24 @@ export const useMailStore = defineStore('mailStore', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findFoldersByRole(role: string): CollectionObject[] {
|
||||||
|
const normalizedRole = role.toLowerCase()
|
||||||
|
|
||||||
|
return servicesStore.servicesEnabled.flatMap(service => {
|
||||||
|
if (service.identifier === null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return collectionsStore.collectionsForService(service.provider, service.identifier).filter(
|
||||||
|
folder =>
|
||||||
|
folder.provider === service.provider &&
|
||||||
|
String(folder.service) === String(service.identifier) &&
|
||||||
|
(folder.properties.role === normalizedRole ||
|
||||||
|
String(folder.identifier).toLowerCase() === normalizedRole),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async function sendComposerMessage(message: ComposerMessageInput) {
|
async function sendComposerMessage(message: ComposerMessageInput) {
|
||||||
composerSending.value = true
|
composerSending.value = true
|
||||||
|
|
||||||
@@ -492,7 +475,7 @@ export const useMailStore = defineStore('mailStore', () => {
|
|||||||
const deletedFolder = await collectionsStore.delete(folder.identifier)
|
const deletedFolder = await collectionsStore.delete(folder.identifier)
|
||||||
|
|
||||||
if (_sameCollection(selectedFolder.value, folder)) {
|
if (_sameCollection(selectedFolder.value, folder)) {
|
||||||
clearSelectedFolder()
|
await selectFolder(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
notify(
|
notify(
|
||||||
@@ -503,6 +486,81 @@ export const useMailStore = defineStore('mailStore', () => {
|
|||||||
return deletedFolder
|
return deletedFolder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteMessages(entityIdentifiers: EntityIdentifier[]) {
|
||||||
|
if (entityIdentifiers.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { successes, failures } = await entitiesStore.delete(entityIdentifiers)
|
||||||
|
|
||||||
|
if (failures.length === 0) {
|
||||||
|
notify(
|
||||||
|
successes.length === 1 ? 'Message deleted' : `${successes.length} messages deleted`,
|
||||||
|
'success',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failures.length > 0) {
|
||||||
|
notify(
|
||||||
|
successes.length === 0
|
||||||
|
? `Delete failed for ${failures.length === 1 ? '1 message' : `${failures.length} messages`}`
|
||||||
|
: `Deleted ${successes.length} ${successes.length === 1 ? 'message' : 'messages'}. ${failures.length} failed.`,
|
||||||
|
successes.length === 0 ? 'error' : 'warning',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const messageText = error instanceof Error ? error.message : 'Failed to delete messages'
|
||||||
|
console.error('[Mail][Operations] Failed to delete messages:', error)
|
||||||
|
notify(messageText, 'error')
|
||||||
|
throw error
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function flagMessages(entityIdentifiers: EntityIdentifier[], flags: Partial<MessageInterface['flags']>, options: { notify?: boolean } = {}) {
|
||||||
|
if (entityIdentifiers.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldNotify = options.notify ?? true
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const patch = entitiesStore.fresh().properties
|
||||||
|
patch.flags = flags
|
||||||
|
|
||||||
|
const { successes, failures } = await entitiesStore.patch(patch, entityIdentifiers)
|
||||||
|
|
||||||
|
if (shouldNotify && successes.length > 0) {
|
||||||
|
notify(
|
||||||
|
successes.length === 1 ? 'Message updated' : `${successes.length} messages updated`,
|
||||||
|
'success',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldNotify && failures.length > 0) {
|
||||||
|
notify(
|
||||||
|
successes.length === 0
|
||||||
|
? `Update failed for ${failures.length === 1 ? '1 message' : `${failures.length} messages`}`
|
||||||
|
: `Updated ${successes.length} ${successes.length === 1 ? 'message' : 'messages'}. ${failures.length} failed.`,
|
||||||
|
successes.length === 0 ? 'error' : 'warning',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const messageText = error instanceof Error ? error.message : 'Failed to update messages'
|
||||||
|
console.error('[Mail][Operations] Failed to update messages:', error)
|
||||||
|
notify(messageText, 'error')
|
||||||
|
throw error
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function moveMessages(target: CollectionObject, entityIdentifiers: EntityIdentifier[]) {
|
async function moveMessages(target: CollectionObject, entityIdentifiers: EntityIdentifier[]) {
|
||||||
const { movableIdentifiers, sourceCollections } = entityIdentifiers.reduce(
|
const { movableIdentifiers, sourceCollections } = entityIdentifiers.reduce(
|
||||||
(accumulator, identifier) => {
|
(accumulator, identifier) => {
|
||||||
@@ -575,41 +633,6 @@ export const useMailStore = defineStore('mailStore', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteMessages(entityIdentifiers: EntityIdentifier[]) {
|
|
||||||
if (entityIdentifiers.length === 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loading.value = true
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { successes, failures } = await entitiesStore.delete(entityIdentifiers)
|
|
||||||
|
|
||||||
if (failures.length === 0) {
|
|
||||||
notify(
|
|
||||||
successes.length === 1 ? 'Message deleted' : `${successes.length} messages deleted`,
|
|
||||||
'success',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failures.length > 0) {
|
|
||||||
notify(
|
|
||||||
successes.length === 0
|
|
||||||
? `Delete failed for ${failures.length === 1 ? '1 message' : `${failures.length} messages`}`
|
|
||||||
: `Deleted ${successes.length} ${successes.length === 1 ? 'message' : 'messages'}. ${failures.length} failed.`,
|
|
||||||
successes.length === 0 ? 'error' : 'warning',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const messageText = error instanceof Error ? error.message : 'Failed to delete messages'
|
|
||||||
console.error('[Mail][Operations] Failed to delete messages:', error)
|
|
||||||
notify(messageText, 'error')
|
|
||||||
throw error
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function notify(message: string, color: 'success' | 'error' | 'info' | 'warning' = 'success') {
|
function notify(message: string, color: 'success' | 'error' | 'info' | 'warning' = 'success') {
|
||||||
showSnackbar({ message, color })
|
showSnackbar({ message, color })
|
||||||
}
|
}
|
||||||
@@ -625,7 +648,6 @@ export const useMailStore = defineStore('mailStore', () => {
|
|||||||
|
|
||||||
// State
|
// State
|
||||||
loading,
|
loading,
|
||||||
selectedFolder,
|
|
||||||
selectedMessage,
|
selectedMessage,
|
||||||
composerSaving,
|
composerSaving,
|
||||||
composerSending,
|
composerSending,
|
||||||
@@ -641,14 +663,12 @@ export const useMailStore = defineStore('mailStore', () => {
|
|||||||
// Actions
|
// Actions
|
||||||
retrieveService,
|
retrieveService,
|
||||||
selectFolder,
|
selectFolder,
|
||||||
clearSelectedFolder,
|
|
||||||
selectMessage,
|
selectMessage,
|
||||||
clearSelectedMessage,
|
|
||||||
createFolder,
|
createFolder,
|
||||||
reloadSelectedFolder,
|
|
||||||
saveComposerDraft,
|
saveComposerDraft,
|
||||||
sendComposerMessage,
|
sendComposerMessage,
|
||||||
resetComposerState,
|
resetComposerState,
|
||||||
|
flagMessages,
|
||||||
deleteMessages,
|
deleteMessages,
|
||||||
deleteFolder,
|
deleteFolder,
|
||||||
moveMessages,
|
moveMessages,
|
||||||
@@ -658,6 +678,7 @@ export const useMailStore = defineStore('mailStore', () => {
|
|||||||
isServiceFolderLoading,
|
isServiceFolderLoading,
|
||||||
hasServiceFoldersLoaded,
|
hasServiceFoldersLoaded,
|
||||||
getServiceFolderError,
|
getServiceFolderError,
|
||||||
|
findFoldersByRole,
|
||||||
loadFoldersForService,
|
loadFoldersForService,
|
||||||
initialize,
|
initialize,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,20 @@ import { computed, ref, shallowRef, watch } from 'vue'
|
|||||||
import { defineStore } from 'pinia'
|
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 type { ServiceIdentifier, EntityIdentifier } from '@MailManager/types/common'
|
import type { ServiceIdentifier, EntityIdentifier } from '@MailManager/types/common'
|
||||||
import type { EntityObject, 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'
|
||||||
|
|
||||||
export const useMailUiStore = defineStore('mailUiStore', () => {
|
export const useMailUiStore = defineStore('mailUiStore', () => {
|
||||||
const collectionsStore = useCollectionsStore()
|
const collectionsStore = useCollectionsStore()
|
||||||
const mailStore = useMailStore()
|
const mailStore = useMailStore()
|
||||||
|
const mailSettingsStore = useMailSettingsStore()
|
||||||
|
|
||||||
const sidebarVisible = ref(true)
|
const sidebarVisible = ref(true)
|
||||||
const settingsDialogVisible = ref(false)
|
const settingsDialogVisible = ref(false)
|
||||||
|
const selectedFolder = shallowRef<CollectionObject | null>(null)
|
||||||
|
const selectedMessage = shallowRef<EntityObject | null>(null)
|
||||||
const composeMode = ref<'new' | 'reply' | 'forward'>('new')
|
const composeMode = ref<'new' | 'reply' | 'forward'>('new')
|
||||||
const composeSource = shallowRef<EntityObject | null>(null)
|
const composeSource = shallowRef<EntityObject | null>(null)
|
||||||
const composeVisible = ref(false)
|
const composeVisible = ref(false)
|
||||||
@@ -38,6 +42,8 @@ export const useMailUiStore = defineStore('mailUiStore', () => {
|
|||||||
const deleteFolderDialogFolder = shallowRef<CollectionObject | null>(null)
|
const deleteFolderDialogFolder = shallowRef<CollectionObject | null>(null)
|
||||||
const deleteFolderDialogLoading = ref(false)
|
const deleteFolderDialogLoading = ref(false)
|
||||||
const deleteFolderDialogError = ref('')
|
const deleteFolderDialogError = ref('')
|
||||||
|
const messageReadIdentifier = ref<EntityIdentifier | null>(null)
|
||||||
|
const messageReadTimer = ref<ReturnType<typeof setTimeout> | null>(null)
|
||||||
|
|
||||||
const createFolderDialogParentLabel = computed(() => {
|
const createFolderDialogParentLabel = computed(() => {
|
||||||
return createFolderDialogParent.value?.properties.label || 'Root'
|
return createFolderDialogParent.value?.properties.label || 'Root'
|
||||||
@@ -86,20 +92,14 @@ export const useMailUiStore = defineStore('mailUiStore', () => {
|
|||||||
return Array.from(invalidKeys)
|
return Array.from(invalidKeys)
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
|
||||||
() => mailStore.selectedFolder,
|
|
||||||
() => {
|
|
||||||
closeCompose()
|
|
||||||
deactivateSelectionMode()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => mailStore.selectedMessage,
|
() => mailStore.selectedMessage,
|
||||||
selectedMessage => {
|
message => {
|
||||||
if (selectedMessage) {
|
if (message) {
|
||||||
closeCompose()
|
closeCompose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectMessage(message)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -110,6 +110,260 @@ export const useMailUiStore = defineStore('mailUiStore', () => {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
function sidebarToggle() {
|
||||||
|
sidebarVisible.value = !sidebarVisible.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function sidebarHide() {
|
||||||
|
sidebarVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function settingsOpen() {
|
||||||
|
settingsDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function settingsClose() {
|
||||||
|
settingsDialogVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function _sameCollection(left: CollectionObject | null | undefined, right: CollectionObject | null | undefined): boolean {
|
||||||
|
if (!left || !right) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return left.provider === right.provider &&
|
||||||
|
String(left.service) === String(right.service) &&
|
||||||
|
String(left.identifier) === String(right.identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initialize() {
|
||||||
|
await mailStore.initialize()
|
||||||
|
|
||||||
|
if (!selectedFolder.value) {
|
||||||
|
const inbox = mailStore.findFoldersByRole('inbox')[0] ?? null
|
||||||
|
|
||||||
|
if (inbox) {
|
||||||
|
await selectFolder(inbox)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function selectFolder(folder: CollectionObject | null) {
|
||||||
|
closeCompose()
|
||||||
|
messageSelectionModeDeactivate()
|
||||||
|
clearMessageReadTimer()
|
||||||
|
selectedFolder.value = folder
|
||||||
|
await mailStore.selectFolder(folder)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function selectMessage(message: EntityObject | null) {
|
||||||
|
closeCompose()
|
||||||
|
messageSelectionModeDeactivate()
|
||||||
|
createMessageReadTimer(message)
|
||||||
|
// mailStore.selectMessage(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMessageReadTimer(entity: EntityObject | null) {
|
||||||
|
clearMessageReadTimer()
|
||||||
|
|
||||||
|
if (!entity) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity.properties.isRead || !mailSettingsStore.messageReadEnabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const delayMilliseconds = mailSettingsStore.messageReadDelay * 1000
|
||||||
|
|
||||||
|
if (delayMilliseconds <= 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
messageReadIdentifier.value = entity.identifier
|
||||||
|
messageReadTimer.value = setTimeout(() => {
|
||||||
|
void completeMessageRead(entity.identifier)
|
||||||
|
}, delayMilliseconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearMessageReadTimer() {
|
||||||
|
if (messageReadTimer.value !== null) {
|
||||||
|
clearTimeout(messageReadTimer.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
messageReadTimer.value = null
|
||||||
|
messageReadIdentifier.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
async function completeMessageRead(identifier: EntityIdentifier) {
|
||||||
|
try {
|
||||||
|
if (selectedMessage.value && selectedMessage.value.identifier === identifier && selectedMessage.value.properties.isRead === false) {
|
||||||
|
await mailStore.flagMessages([selectedMessage.value.identifier], { read: true }, { notify: false })
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Mail][UI] Failed to auto-mark message as read:', error)
|
||||||
|
} finally {
|
||||||
|
clearMessageReadTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCreateFolderDialog(service: ServiceObject, parentFolder: CollectionObject | null = null) {
|
||||||
|
createFolderDialogService.value = service
|
||||||
|
createFolderDialogParent.value = parentFolder
|
||||||
|
createFolderDialogError.value = ''
|
||||||
|
createFolderDialogLoading.value = false
|
||||||
|
createFolderDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeCreateFolderDialog() {
|
||||||
|
createFolderDialogVisible.value = false
|
||||||
|
createFolderDialogService.value = null
|
||||||
|
createFolderDialogParent.value = null
|
||||||
|
createFolderDialogError.value = ''
|
||||||
|
createFolderDialogLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmCreateFolder(label: string) {
|
||||||
|
const service = createFolderDialogService.value
|
||||||
|
|
||||||
|
if (!service) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
createFolderDialogLoading.value = true
|
||||||
|
createFolderDialogError.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const folder = await mailStore.createFolder(service, label, createFolderDialogParent.value)
|
||||||
|
closeCreateFolderDialog()
|
||||||
|
return folder
|
||||||
|
} catch (error) {
|
||||||
|
createFolderDialogError.value = error instanceof Error ? error.message : 'Failed to create folder. Please try again.'
|
||||||
|
throw error
|
||||||
|
} finally {
|
||||||
|
createFolderDialogLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openRenameFolderDialog(target: CollectionObject) {
|
||||||
|
const service = await mailStore.retrieveService(target.service)
|
||||||
|
renameFolderDialogService.value = service
|
||||||
|
renameFolderDialogFolder.value = target
|
||||||
|
renameFolderDialogError.value = ''
|
||||||
|
renameFolderDialogLoading.value = false
|
||||||
|
renameFolderDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeRenameFolderDialog() {
|
||||||
|
renameFolderDialogVisible.value = false
|
||||||
|
renameFolderDialogService.value = null
|
||||||
|
renameFolderDialogFolder.value = null
|
||||||
|
renameFolderDialogError.value = ''
|
||||||
|
renameFolderDialogLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmRenameFolder(label: string) {
|
||||||
|
const folder = renameFolderDialogFolder.value
|
||||||
|
|
||||||
|
if (!folder) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
renameFolderDialogLoading.value = true
|
||||||
|
renameFolderDialogError.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updatedFolder = await mailStore.renameFolder(folder, label)
|
||||||
|
|
||||||
|
if (_sameCollection(selectedFolder.value, folder)) {
|
||||||
|
selectedFolder.value = updatedFolder
|
||||||
|
}
|
||||||
|
|
||||||
|
closeRenameFolderDialog()
|
||||||
|
return updatedFolder
|
||||||
|
} catch (error) {
|
||||||
|
renameFolderDialogError.value = error instanceof Error ? error.message : 'Failed to rename folder. Please try again.'
|
||||||
|
throw error
|
||||||
|
} finally {
|
||||||
|
renameFolderDialogLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openMoveFolderDialog(source: CollectionObject) {
|
||||||
|
const service = await mailStore.retrieveService(source.service)
|
||||||
|
moveFolderDialogService.value = service
|
||||||
|
moveFolderDialogSource.value = source
|
||||||
|
moveFolderDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeMoveFolderDialog() {
|
||||||
|
moveFolderDialogVisible.value = false
|
||||||
|
moveFolderDialogService.value = null
|
||||||
|
moveFolderDialogSource.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmMoveFolder(target: CollectionObject) {
|
||||||
|
const source = moveFolderDialogSource.value
|
||||||
|
|
||||||
|
if (!source) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const movedFolder = await mailStore.moveFolder(source, target)
|
||||||
|
|
||||||
|
if (_sameCollection(selectedFolder.value, source)) {
|
||||||
|
selectedFolder.value = movedFolder
|
||||||
|
}
|
||||||
|
|
||||||
|
closeMoveFolderDialog()
|
||||||
|
return movedFolder
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openDeleteFolderDialog(target: CollectionObject) {
|
||||||
|
const service = await mailStore.retrieveService(target.service)
|
||||||
|
deleteFolderDialogService.value = service
|
||||||
|
deleteFolderDialogFolder.value = target
|
||||||
|
deleteFolderDialogError.value = ''
|
||||||
|
deleteFolderDialogLoading.value = false
|
||||||
|
deleteFolderDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDeleteFolderDialog() {
|
||||||
|
deleteFolderDialogVisible.value = false
|
||||||
|
deleteFolderDialogService.value = null
|
||||||
|
deleteFolderDialogFolder.value = null
|
||||||
|
deleteFolderDialogError.value = ''
|
||||||
|
deleteFolderDialogLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmDeleteFolder() {
|
||||||
|
const folder = deleteFolderDialogFolder.value
|
||||||
|
|
||||||
|
if (!folder) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteFolderDialogLoading.value = true
|
||||||
|
deleteFolderDialogError.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deleted = await mailStore.deleteFolder(folder)
|
||||||
|
|
||||||
|
if (_sameCollection(selectedFolder.value, folder)) {
|
||||||
|
selectFolder(null)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDeleteFolderDialog()
|
||||||
|
return deleted
|
||||||
|
} catch (error) {
|
||||||
|
deleteFolderDialogError.value = error instanceof Error ? error.message : 'Failed to delete folder. Please try again.'
|
||||||
|
throw error
|
||||||
|
} finally {
|
||||||
|
deleteFolderDialogLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function validateFolderNameBase(service: ServiceObject, name: string): string[] {
|
function validateFolderNameBase(service: ServiceObject, name: string): string[] {
|
||||||
const errors: string[] = []
|
const errors: string[] = []
|
||||||
|
|
||||||
@@ -196,7 +450,7 @@ export const useMailUiStore = defineStore('mailUiStore', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openCompose(source?: EntityObject, mode: 'reply' | 'forward' = 'reply') {
|
function openCompose(source?: EntityObject, mode: 'reply' | 'forward' = 'reply') {
|
||||||
mailStore.clearSelectedMessage()
|
mailStore.selectMessage(null)
|
||||||
composeSource.value = source ?? null
|
composeSource.value = source ?? null
|
||||||
composeMode.value = mode
|
composeMode.value = mode
|
||||||
composeVisible.value = true
|
composeVisible.value = true
|
||||||
@@ -254,7 +508,7 @@ export const useMailUiStore = defineStore('mailUiStore', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function messageSelectionReconcile() {
|
function messageSelectionReconcile() {
|
||||||
if (!mailStore.selectedFolder) {
|
if (!selectedFolder.value) {
|
||||||
messageSelectionClear()
|
messageSelectionClear()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -271,22 +525,6 @@ export const useMailUiStore = defineStore('mailUiStore', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSidebar() {
|
|
||||||
sidebarVisible.value = !sidebarVisible.value
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeSidebar() {
|
|
||||||
sidebarVisible.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
function openSettings() {
|
|
||||||
settingsDialogVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeSettings() {
|
|
||||||
settingsDialogVisible.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openMoveMessagesDialog(entities?: EntityObject | EntityObject[]) {
|
async function openMoveMessagesDialog(entities?: EntityObject | EntityObject[]) {
|
||||||
|
|
||||||
let moveMessagesServiceIdentifier = null as ServiceIdentifier | null
|
let moveMessagesServiceIdentifier = null as ServiceIdentifier | null
|
||||||
@@ -302,10 +540,10 @@ export const useMailUiStore = defineStore('mailUiStore', () => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
moveMessagesDialogCandidates.value = [...selectionList.value]
|
moveMessagesDialogCandidates.value = [...selectionList.value]
|
||||||
moveMessagesServiceIdentifier = mailStore.selectedFolder?.service as ServiceIdentifier || null
|
moveMessagesServiceIdentifier = selectedFolder.value?.service as ServiceIdentifier || null
|
||||||
}
|
}
|
||||||
|
|
||||||
moveMessagesDialogService.value = await mailStore.retrieveService(moveMessagesServiceIdentifier);
|
moveMessagesDialogService.value = await mailStore.retrieveService(moveMessagesServiceIdentifier)
|
||||||
moveMessagesDialogVisible.value = true
|
moveMessagesDialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,153 +561,19 @@ export const useMailUiStore = defineStore('mailUiStore', () => {
|
|||||||
|
|
||||||
async function deleteSelectedMessages() {
|
async function deleteSelectedMessages() {
|
||||||
await mailStore.deleteMessages([...selectionList.value])
|
await mailStore.deleteMessages([...selectionList.value])
|
||||||
deactivateSelectionMode()
|
messageSelectionModeDeactivate()
|
||||||
}
|
}
|
||||||
|
|
||||||
function openCreateFolderDialog(service: ServiceObject, parentFolder: CollectionObject | null = null) {
|
async function flagSelectedMessages(flag: string, value: boolean) {
|
||||||
createFolderDialogService.value = service
|
await mailStore.flagMessages([...selectionList.value], { [flag]: value })
|
||||||
createFolderDialogParent.value = parentFolder
|
messageSelectionModeDeactivate()
|
||||||
createFolderDialogError.value = ''
|
|
||||||
createFolderDialogLoading.value = false
|
|
||||||
createFolderDialogVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeCreateFolderDialog() {
|
|
||||||
createFolderDialogVisible.value = false
|
|
||||||
createFolderDialogService.value = null
|
|
||||||
createFolderDialogParent.value = null
|
|
||||||
createFolderDialogError.value = ''
|
|
||||||
createFolderDialogLoading.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
async function confirmCreateFolder(label: string) {
|
|
||||||
const service = createFolderDialogService.value
|
|
||||||
|
|
||||||
if (!service) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
createFolderDialogLoading.value = true
|
|
||||||
createFolderDialogError.value = ''
|
|
||||||
|
|
||||||
try {
|
|
||||||
const folder = await mailStore.createFolder(service, label, createFolderDialogParent.value)
|
|
||||||
closeCreateFolderDialog()
|
|
||||||
return folder
|
|
||||||
} catch (error) {
|
|
||||||
createFolderDialogError.value = error instanceof Error ? error.message : 'Failed to create folder. Please try again.'
|
|
||||||
throw error
|
|
||||||
} finally {
|
|
||||||
createFolderDialogLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openRenameFolderDialog(target: CollectionObject) {
|
|
||||||
const service = await mailStore.retrieveService(target.service)
|
|
||||||
renameFolderDialogService.value = service
|
|
||||||
renameFolderDialogFolder.value = target
|
|
||||||
renameFolderDialogError.value = ''
|
|
||||||
renameFolderDialogLoading.value = false
|
|
||||||
renameFolderDialogVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeRenameFolderDialog() {
|
|
||||||
renameFolderDialogVisible.value = false
|
|
||||||
renameFolderDialogService.value = null
|
|
||||||
renameFolderDialogFolder.value = null
|
|
||||||
renameFolderDialogError.value = ''
|
|
||||||
renameFolderDialogLoading.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
async function confirmRenameFolder(label: string) {
|
|
||||||
const folder = renameFolderDialogFolder.value
|
|
||||||
|
|
||||||
if (!folder) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
renameFolderDialogLoading.value = true
|
|
||||||
renameFolderDialogError.value = ''
|
|
||||||
|
|
||||||
try {
|
|
||||||
const updatedFolder = await mailStore.renameFolder(folder, label)
|
|
||||||
closeRenameFolderDialog()
|
|
||||||
return updatedFolder
|
|
||||||
} catch (error) {
|
|
||||||
renameFolderDialogError.value = error instanceof Error ? error.message : 'Failed to rename folder. Please try again.'
|
|
||||||
throw error
|
|
||||||
} finally {
|
|
||||||
renameFolderDialogLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openMoveFolderDialog(source: CollectionObject) {
|
|
||||||
const service = await mailStore.retrieveService(source.service)
|
|
||||||
moveFolderDialogService.value = service
|
|
||||||
moveFolderDialogSource.value = source
|
|
||||||
moveFolderDialogVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeMoveFolderDialog() {
|
|
||||||
moveFolderDialogVisible.value = false
|
|
||||||
moveFolderDialogService.value = null
|
|
||||||
moveFolderDialogSource.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
async function confirmMoveFolder(target: CollectionObject) {
|
|
||||||
const source = moveFolderDialogSource.value
|
|
||||||
|
|
||||||
if (!source) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const movedFolder = await mailStore.moveFolder(source, target)
|
|
||||||
closeMoveFolderDialog()
|
|
||||||
return movedFolder
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openDeleteFolderDialog(target: CollectionObject) {
|
|
||||||
const service = await mailStore.retrieveService(target.service)
|
|
||||||
deleteFolderDialogService.value = service
|
|
||||||
deleteFolderDialogFolder.value = target
|
|
||||||
deleteFolderDialogError.value = ''
|
|
||||||
deleteFolderDialogLoading.value = false
|
|
||||||
deleteFolderDialogVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeDeleteFolderDialog() {
|
|
||||||
deleteFolderDialogVisible.value = false
|
|
||||||
deleteFolderDialogService.value = null
|
|
||||||
deleteFolderDialogFolder.value = null
|
|
||||||
deleteFolderDialogError.value = ''
|
|
||||||
deleteFolderDialogLoading.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
async function confirmDeleteFolder() {
|
|
||||||
const folder = deleteFolderDialogFolder.value
|
|
||||||
|
|
||||||
if (!folder) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteFolderDialogLoading.value = true
|
|
||||||
deleteFolderDialogError.value = ''
|
|
||||||
|
|
||||||
try {
|
|
||||||
const deleted = await mailStore.deleteFolder(folder)
|
|
||||||
closeDeleteFolderDialog()
|
|
||||||
return deleted
|
|
||||||
} catch (error) {
|
|
||||||
deleteFolderDialogError.value = error instanceof Error ? error.message : 'Failed to delete folder. Please try again.'
|
|
||||||
throw error
|
|
||||||
} finally {
|
|
||||||
deleteFolderDialogLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sidebarVisible,
|
sidebarVisible,
|
||||||
settingsDialogVisible,
|
settingsDialogVisible,
|
||||||
|
selectedFolder,
|
||||||
|
selectedMessage,
|
||||||
composeMode,
|
composeMode,
|
||||||
composeSource,
|
composeSource,
|
||||||
composeVisible,
|
composeVisible,
|
||||||
@@ -499,13 +603,14 @@ export const useMailUiStore = defineStore('mailUiStore', () => {
|
|||||||
deleteFolderDialogFolder,
|
deleteFolderDialogFolder,
|
||||||
deleteFolderDialogLoading,
|
deleteFolderDialogLoading,
|
||||||
deleteFolderDialogError,
|
deleteFolderDialogError,
|
||||||
toggleSidebar,
|
sidebarToggle,
|
||||||
closeSidebar,
|
sidebarHide,
|
||||||
openSettings,
|
settingsOpen,
|
||||||
closeSettings,
|
settingsClose,
|
||||||
|
initialize,
|
||||||
|
selectFolder,
|
||||||
openCompose,
|
openCompose,
|
||||||
closeCompose,
|
closeCompose,
|
||||||
afterSent,
|
|
||||||
messageSelectionModeActivate,
|
messageSelectionModeActivate,
|
||||||
messageSelectionModeDeactivate,
|
messageSelectionModeDeactivate,
|
||||||
messageSelectionToggleOne,
|
messageSelectionToggleOne,
|
||||||
@@ -529,5 +634,6 @@ export const useMailUiStore = defineStore('mailUiStore', () => {
|
|||||||
openDeleteFolderDialog,
|
openDeleteFolderDialog,
|
||||||
closeDeleteFolderDialog,
|
closeDeleteFolderDialog,
|
||||||
confirmDeleteFolder,
|
confirmDeleteFolder,
|
||||||
|
flagSelectedMessages,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
Reference in New Issue
Block a user