Merge pull request 'refactor: use module store' (#8) from refactor/use-store into main
Reviewed-on: #8
This commit was merged in pull request #8.
This commit is contained in:
@@ -116,6 +116,11 @@ const readCount = computed(() => {
|
|||||||
const totalCount = computed(() => {
|
const totalCount = computed(() => {
|
||||||
return props.selectedCollection?.properties.total ?? 0
|
return props.selectedCollection?.properties.total ?? 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// True only when the collection explicitly provides total/unread counts
|
||||||
|
const hasCountData = computed(() => {
|
||||||
|
return props.selectedCollection?.properties.total != null
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -124,13 +129,13 @@ const totalCount = computed(() => {
|
|||||||
<div v-if="selectedCollection" class="message-list-header">
|
<div v-if="selectedCollection" class="message-list-header">
|
||||||
<h2 class="text-h6">{{ selectedCollection.properties.label || 'Folder' }}</h2>
|
<h2 class="text-h6">{{ selectedCollection.properties.label || 'Folder' }}</h2>
|
||||||
<div class="folder-counts text-caption text-medium-emphasis">
|
<div class="folder-counts text-caption text-medium-emphasis">
|
||||||
<span v-if="totalCount > 0">
|
<span v-if="hasCountData">
|
||||||
<span class="unread-count">{{ unreadCount }}</span>
|
<span class="unread-count">{{ unreadCount }}</span>
|
||||||
<span class="mx-1">/</span>
|
<span class="mx-1">/</span>
|
||||||
<span>{{ totalCount }}</span>
|
<span>{{ totalCount }}</span>
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<span v-else-if="messages.length > 0">
|
||||||
Empty
|
{{ messages.length }} loaded
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ const integrations: ModuleIntegrations = {
|
|||||||
label: 'Mail',
|
label: 'Mail',
|
||||||
path: '/',
|
path: '/',
|
||||||
icon: 'mdi-email',
|
icon: 'mdi-email',
|
||||||
priority: 15,
|
priority: 15
|
||||||
caption: 'Email client'
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,22 +1,20 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, watch } from 'vue'
|
import { computed, onMounted } from 'vue'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
import { useModuleStore } from '@KTXC'
|
import { useModuleStore } from '@KTXC'
|
||||||
import { useCollectionsStore } from '@MailManager/stores/collectionsStore'
|
import { useMailStore } from '@/stores/mailStore'
|
||||||
import { useEntitiesStore } from '@MailManager/stores/entitiesStore'
|
|
||||||
import { useServicesStore } from '@MailManager/stores/servicesStore'
|
|
||||||
import { useMailSync } from '@MailManager/composables/useMailSync'
|
|
||||||
import type { CollectionObject } from '@MailManager/models/collection'
|
|
||||||
import type { EntityInterface } from '@MailManager/types/entity'
|
|
||||||
import type { MessageInterface } from '@MailManager/types/message'
|
|
||||||
import FolderTree from '@/components/FolderTree.vue'
|
import FolderTree from '@/components/FolderTree.vue'
|
||||||
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'
|
||||||
import SettingsDialog from '@/components/settings/SettingsDialog.vue'
|
import SettingsDialog from '@/components/settings/SettingsDialog.vue'
|
||||||
|
import type { EntityInterface } from '@MailManager/types/entity'
|
||||||
|
import type { MessageInterface } from '@MailManager/types/message'
|
||||||
|
|
||||||
// Vuetify display for responsive behavior
|
// Vuetify display for responsive behavior
|
||||||
const display = useDisplay()
|
const display = useDisplay()
|
||||||
|
const isMobile = computed(() => display.mdAndDown.value)
|
||||||
|
|
||||||
// Check if mail manager is available
|
// Check if mail manager is available
|
||||||
const moduleStore = useModuleStore()
|
const moduleStore = useModuleStore()
|
||||||
@@ -24,194 +22,59 @@ const isMailManagerAvailable = computed(() => {
|
|||||||
return moduleStore.has('mail_manager') || moduleStore.has('MailManager')
|
return moduleStore.has('mail_manager') || moduleStore.has('MailManager')
|
||||||
})
|
})
|
||||||
|
|
||||||
// Snackbar state for notifications
|
// Mail store — single source of truth for all mail UI state
|
||||||
const snackbarVisible = ref(false)
|
const mailStore = useMailStore()
|
||||||
const snackbarMessage = ref('')
|
|
||||||
const snackbarColor = ref('success')
|
|
||||||
|
|
||||||
// Stores
|
// storeToRefs preserves reactivity for state and computed properties
|
||||||
const collectionsStore = useCollectionsStore()
|
const {
|
||||||
const entitiesStore = useEntitiesStore()
|
sidebarVisible,
|
||||||
const servicesStore = useServicesStore()
|
settingsDialogVisible,
|
||||||
|
loading,
|
||||||
|
selectedFolder,
|
||||||
|
selectedMessage,
|
||||||
|
composeMode,
|
||||||
|
composeReplyTo,
|
||||||
|
snackbarVisible,
|
||||||
|
snackbarMessage,
|
||||||
|
snackbarColor,
|
||||||
|
currentMessages,
|
||||||
|
} = storeToRefs(mailStore)
|
||||||
|
|
||||||
// Background mail sync
|
// Complex store/composable objects accessed directly (not simple refs)
|
||||||
const mailSync = useMailSync({
|
const { mailSync, entitiesStore } = mailStore
|
||||||
interval: 30000, // Check every 30 seconds
|
|
||||||
autoStart: false, // We'll start it manually after initialization
|
|
||||||
fetchDetails: true, // Auto-fetch full message details for new/modified messages
|
|
||||||
})
|
|
||||||
|
|
||||||
// UI state
|
|
||||||
const sidebarVisible = ref(true)
|
|
||||||
const selectedFolder = ref<CollectionObject | null>(null)
|
|
||||||
const selectedMessage = ref<EntityInterface<MessageInterface> | null>(null)
|
|
||||||
const composeMode = ref(false)
|
|
||||||
const composeReplyTo = ref<EntityInterface<MessageInterface> | null>(null)
|
|
||||||
const settingsDialogVisible = ref(false)
|
|
||||||
|
|
||||||
// Loading state
|
|
||||||
const loading = ref(false)
|
|
||||||
|
|
||||||
// Computed
|
|
||||||
const isMobile = computed(() => display.mdAndDown.value)
|
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (!isMailManagerAvailable.value) return
|
if (!isMailManagerAvailable.value) return
|
||||||
|
await mailStore.initialize()
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
// Load services (accounts)
|
|
||||||
await servicesStore.list()
|
|
||||||
|
|
||||||
// Load collections (folders)
|
|
||||||
await collectionsStore.list()
|
|
||||||
|
|
||||||
// Select inbox by default if available
|
|
||||||
const inbox = collectionsStore.collections.find(c => c.properties.role === 'inbox')
|
|
||||||
if (inbox) {
|
|
||||||
handleFolderSelect(inbox)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start background sync after initialization
|
|
||||||
mailSync.start()
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[Mail] Failed to initialize:', error)
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Watch for folder and service changes to update sync sources
|
// Handlers — thin wrappers that delegate to the store
|
||||||
watch(
|
const handleFolderSelect = (folder: Parameters<typeof mailStore.selectFolder>[0]) =>
|
||||||
[selectedFolder, () => servicesStore.services],
|
mailStore.selectFolder(folder)
|
||||||
() => {
|
|
||||||
if (!isMailManagerAvailable.value) return
|
|
||||||
|
|
||||||
mailSync.clearSources()
|
const handleMessageSelect = (message: EntityInterface<MessageInterface>) =>
|
||||||
|
mailStore.selectMessage(message, isMobile.value)
|
||||||
|
|
||||||
// Add currently selected folder to sync
|
const handleCompose = (replyTo?: EntityInterface<MessageInterface>) =>
|
||||||
if (selectedFolder.value) {
|
mailStore.openCompose(replyTo)
|
||||||
mailSync.addSource({
|
|
||||||
provider: selectedFolder.value.provider,
|
|
||||||
service: selectedFolder.value.service,
|
|
||||||
collections: [selectedFolder.value.identifier],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add inbox for each service to get notifications
|
const handleComposeClose = () => mailStore.closeCompose()
|
||||||
servicesStore.services.forEach(service => {
|
|
||||||
// Find inbox collection for this service
|
|
||||||
const inboxes = collectionsStore.collections.filter(
|
|
||||||
c => c.service === service.identifier &&
|
|
||||||
(c.properties.role === 'inbox' ||
|
|
||||||
String(c.identifier).toLowerCase() === 'inbox')
|
|
||||||
)
|
|
||||||
|
|
||||||
if (inboxes.length > 0) {
|
const handleComposeSent = () => mailStore.afterSent()
|
||||||
mailSync.addSource({
|
|
||||||
provider: service.provider,
|
|
||||||
service: service.identifier as string | number,
|
|
||||||
collections: inboxes.map(inbox => inbox.identifier),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Restart sync with updated sources
|
const handleReply = (message: EntityInterface<MessageInterface>) =>
|
||||||
if (mailSync.sources.value.length > 0 && !mailSync.isRunning.value) {
|
mailStore.openCompose(message)
|
||||||
mailSync.start()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handlers
|
const handleDelete = (message: EntityInterface<MessageInterface>) =>
|
||||||
const handleFolderSelect = async (folder: CollectionObject) => {
|
mailStore.deleteMessage(message)
|
||||||
selectedFolder.value = folder
|
|
||||||
selectedMessage.value = null
|
|
||||||
composeMode.value = false
|
|
||||||
|
|
||||||
// Load messages for this folder
|
|
||||||
try {
|
|
||||||
await entitiesStore.list({
|
|
||||||
[folder.provider]: {
|
|
||||||
[folder.service]: {
|
|
||||||
[folder.identifier]: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[Mail] Failed to load messages:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleMessageSelect = (message: EntityInterface<MessageInterface>) => {
|
const toggleSidebar = () => mailStore.toggleSidebar()
|
||||||
selectedMessage.value = message
|
|
||||||
composeMode.value = false
|
|
||||||
|
|
||||||
// Close sidebar on mobile after selection
|
|
||||||
if (isMobile.value) {
|
|
||||||
sidebarVisible.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCompose = (replyTo?: EntityInterface<MessageInterface>) => {
|
const handleSettingsOpen = () => mailStore.openSettings()
|
||||||
composeMode.value = true
|
|
||||||
composeReplyTo.value = replyTo || null
|
|
||||||
selectedMessage.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleComposeClose = () => {
|
const handleFolderCreated = (folder: Parameters<typeof mailStore.onFolderCreated>[0]) =>
|
||||||
composeMode.value = false
|
mailStore.onFolderCreated(folder)
|
||||||
composeReplyTo.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleComposeSent = () => {
|
|
||||||
composeMode.value = false
|
|
||||||
composeReplyTo.value = null
|
|
||||||
|
|
||||||
// Reload current folder to show sent message in Sent folder
|
|
||||||
if (selectedFolder.value) {
|
|
||||||
handleFolderSelect(selectedFolder.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleReply = (message: EntityInterface<MessageInterface>) => {
|
|
||||||
handleCompose(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDelete = async (message: EntityInterface<MessageInterface>) => {
|
|
||||||
// TODO: Implement delete functionality
|
|
||||||
console.log('[Mail] Delete message:', message.identifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleSidebar = () => {
|
|
||||||
sidebarVisible.value = !sidebarVisible.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSettingsOpen = () => {
|
|
||||||
settingsDialogVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleFolderCreated = (folder: CollectionObject) => {
|
|
||||||
snackbarMessage.value = `Folder "${folder.properties.label}" created successfully`
|
|
||||||
snackbarColor.value = 'success'
|
|
||||||
snackbarVisible.value = true
|
|
||||||
|
|
||||||
// Reload collections to ensure UI is in sync
|
|
||||||
collectionsStore.list()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Messages for current folder
|
|
||||||
const currentMessages = computed(() => {
|
|
||||||
if (!selectedFolder.value) return []
|
|
||||||
|
|
||||||
return entitiesStore.entitiesForCollection(
|
|
||||||
selectedFolder.value.provider,
|
|
||||||
selectedFolder.value.service,
|
|
||||||
selectedFolder.value.identifier
|
|
||||||
)
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
233
src/stores/mailStore.ts
Normal file
233
src/stores/mailStore.ts
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
import { ref, computed, shallowRef } from 'vue'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { useCollectionsStore } from '@MailManager/stores/collectionsStore'
|
||||||
|
import { useEntitiesStore } from '@MailManager/stores/entitiesStore'
|
||||||
|
import { useServicesStore } from '@MailManager/stores/servicesStore'
|
||||||
|
import { useMailSync } from '@MailManager/composables/useMailSync'
|
||||||
|
import type { CollectionObject } from '@MailManager/models/collection'
|
||||||
|
import type { EntityInterface } from '@MailManager/types/entity'
|
||||||
|
import type { MessageInterface } from '@MailManager/types/message'
|
||||||
|
|
||||||
|
export const useMailStore = defineStore('mailStore', () => {
|
||||||
|
const collectionsStore = useCollectionsStore()
|
||||||
|
const entitiesStore = useEntitiesStore()
|
||||||
|
const servicesStore = useServicesStore()
|
||||||
|
|
||||||
|
// Background mail sync
|
||||||
|
const mailSync = useMailSync({
|
||||||
|
interval: 30000,
|
||||||
|
autoStart: false,
|
||||||
|
fetchDetails: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// ── UI State ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const sidebarVisible = ref(true)
|
||||||
|
const settingsDialogVisible = ref(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// ── Selection State ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const selectedFolder = shallowRef<CollectionObject | null>(null)
|
||||||
|
const selectedMessage = shallowRef<EntityInterface<MessageInterface> | null>(null)
|
||||||
|
|
||||||
|
// ── Compose State ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const composeMode = ref(false)
|
||||||
|
const composeReplyTo = shallowRef<EntityInterface<MessageInterface> | null>(null)
|
||||||
|
|
||||||
|
// ── Notification State ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const snackbarVisible = ref(false)
|
||||||
|
const snackbarMessage = ref('')
|
||||||
|
const snackbarColor = ref<'success' | 'error' | 'info' | 'warning'>('success')
|
||||||
|
|
||||||
|
// ── Computed ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const currentMessages = computed(() => {
|
||||||
|
if (!selectedFolder.value) return []
|
||||||
|
|
||||||
|
const folder = selectedFolder.value
|
||||||
|
|
||||||
|
// Access entitiesStore.entities (reactive computed array) so Vue tracks it
|
||||||
|
return entitiesStore.entities.filter(e =>
|
||||||
|
e.provider === folder.provider &&
|
||||||
|
String(e.service) === String(folder.service) &&
|
||||||
|
String(e.collection) === String(folder.identifier),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// ── Sync Helpers ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function _updateSyncSources() {
|
||||||
|
mailSync.clearSources()
|
||||||
|
|
||||||
|
// Track the currently selected folder
|
||||||
|
if (selectedFolder.value) {
|
||||||
|
mailSync.addSource({
|
||||||
|
provider: selectedFolder.value.provider,
|
||||||
|
service: selectedFolder.value.service,
|
||||||
|
collections: [selectedFolder.value.identifier],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always track inboxes for each account (for new-mail notifications)
|
||||||
|
servicesStore.services.forEach(service => {
|
||||||
|
const inboxes = collectionsStore.collections.filter(
|
||||||
|
c =>
|
||||||
|
c.service === service.identifier &&
|
||||||
|
(c.properties.role === 'inbox' ||
|
||||||
|
String(c.identifier).toLowerCase() === 'inbox'),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (inboxes.length > 0) {
|
||||||
|
mailSync.addSource({
|
||||||
|
provider: service.provider,
|
||||||
|
service: service.identifier as string | number,
|
||||||
|
collections: inboxes.map(inbox => inbox.identifier),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (mailSync.sources.value.length > 0 && !mailSync.isRunning.value) {
|
||||||
|
mailSync.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Actions ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function selectFolder(folder: CollectionObject) {
|
||||||
|
selectedFolder.value = folder
|
||||||
|
selectedMessage.value = null
|
||||||
|
composeMode.value = false
|
||||||
|
|
||||||
|
try {
|
||||||
|
await entitiesStore.list({
|
||||||
|
[folder.provider]: {
|
||||||
|
[folder.service]: {
|
||||||
|
[folder.identifier]: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Mail] Failed to load messages:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateSyncSources()
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectMessage(message: EntityInterface<MessageInterface>, closeSidebar = false) {
|
||||||
|
selectedMessage.value = message
|
||||||
|
composeMode.value = false
|
||||||
|
|
||||||
|
if (closeSidebar) {
|
||||||
|
sidebarVisible.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCompose(replyTo?: EntityInterface<MessageInterface>) {
|
||||||
|
composeMode.value = true
|
||||||
|
composeReplyTo.value = replyTo ?? null
|
||||||
|
selectedMessage.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeCompose() {
|
||||||
|
composeMode.value = false
|
||||||
|
composeReplyTo.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
async function afterSent() {
|
||||||
|
composeMode.value = false
|
||||||
|
composeReplyTo.value = null
|
||||||
|
|
||||||
|
// Reload the current folder so the sent message appears in Sent
|
||||||
|
if (selectedFolder.value) {
|
||||||
|
await selectFolder(selectedFolder.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteMessage(message: EntityInterface<MessageInterface>) {
|
||||||
|
// TODO: implement delete via entity / collection store
|
||||||
|
console.log('[Mail] Delete message:', message.identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSidebar() {
|
||||||
|
sidebarVisible.value = !sidebarVisible.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function openSettings() {
|
||||||
|
settingsDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function notify(message: string, color: typeof snackbarColor.value = 'success') {
|
||||||
|
snackbarMessage.value = message
|
||||||
|
snackbarColor.value = color
|
||||||
|
snackbarVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onFolderCreated(folder: CollectionObject) {
|
||||||
|
notify(`Folder "${folder.properties.label}" created successfully`)
|
||||||
|
// Reload collections so the sidebar reflects the new folder
|
||||||
|
await collectionsStore.list()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Initialization ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function initialize() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
await servicesStore.list()
|
||||||
|
await collectionsStore.list()
|
||||||
|
|
||||||
|
// Select inbox by default
|
||||||
|
const inbox = collectionsStore.collections.find(c => c.properties.role === 'inbox')
|
||||||
|
if (inbox) {
|
||||||
|
await selectFolder(inbox)
|
||||||
|
}
|
||||||
|
|
||||||
|
mailSync.start()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Mail] Failed to initialize:', error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Exports ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Sub-stores (forwarded for template convenience)
|
||||||
|
collectionsStore,
|
||||||
|
entitiesStore,
|
||||||
|
servicesStore,
|
||||||
|
mailSync,
|
||||||
|
|
||||||
|
// State
|
||||||
|
sidebarVisible,
|
||||||
|
settingsDialogVisible,
|
||||||
|
loading,
|
||||||
|
selectedFolder,
|
||||||
|
selectedMessage,
|
||||||
|
composeMode,
|
||||||
|
composeReplyTo,
|
||||||
|
snackbarVisible,
|
||||||
|
snackbarMessage,
|
||||||
|
snackbarColor,
|
||||||
|
|
||||||
|
// Computed
|
||||||
|
currentMessages,
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
selectFolder,
|
||||||
|
selectMessage,
|
||||||
|
openCompose,
|
||||||
|
closeCompose,
|
||||||
|
afterSent,
|
||||||
|
deleteMessage,
|
||||||
|
toggleSidebar,
|
||||||
|
openSettings,
|
||||||
|
notify,
|
||||||
|
onFolderCreated,
|
||||||
|
initialize,
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user