feat: entity move

Signed-off-by: Sebastian <krupinski01@gmail.com>
This commit is contained in:
2026-03-27 20:36:56 -04:00
parent cc78926379
commit 78fff8d0ad
6 changed files with 628 additions and 22 deletions

View File

@@ -5,10 +5,9 @@ import { useEntitiesStore } from '@MailManager/stores/entitiesStore'
import { useServicesStore } from '@MailManager/stores/servicesStore'
import { useMailSync } from '@MailManager/composables/useMailSync'
import { useSnackbar } from '@KTXC'
import type { CollectionObject } from '@MailManager/models/collection'
import type { EntityInterface } from '@MailManager/types/entity'
import type { CollectionIdentifier, EntityIdentifier } from '@MailManager/types/common'
import type { MessageInterface } from '@MailManager/types/message'
import type { ServiceObject } from '@MailManager/models'
import type { ServiceObject, CollectionObject, EntityObject } from '@MailManager/models'
export const useMailStore = defineStore('mailStore', () => {
const collectionsStore = useCollectionsStore()
@@ -42,11 +41,15 @@ export const useMailStore = defineStore('mailStore', () => {
// ── Selection State ───────────────────────────────────────────────────────
const selectedFolder = shallowRef<CollectionObject | null>(null)
const selectedMessage = shallowRef<EntityInterface<MessageInterface> | null>(null)
const selectedMessage = shallowRef<EntityObject | null>(null)
// ── Compose State ─────────────────────────────────────────────────────────
const composeMode = ref(false)
const composeReplyTo = shallowRef<EntityInterface<MessageInterface> | null>(null)
const composeReplyTo = shallowRef<EntityObject | null>(null)
// ── Move State ────────────────────────────────────────────────────────────
const moveDialogVisible = ref(false)
const moveMessageCandidate = shallowRef<EntityObject | null>(null)
// ── Computed ──────────────────────────────────────────────────────────────
const currentMessages = computed(() => {
@@ -205,6 +208,27 @@ export const useMailStore = defineStore('mailStore', () => {
return serviceFolderErrorState.value[_serviceKey(provider, service)] ?? null
}
function _entityIdentifier(entity: EntityObject): EntityIdentifier {
return `${entity.provider}:${String(entity.service)}:${String(entity.collection)}:${String(entity.identifier)}` as EntityIdentifier
}
function _collectionIdentifier(collection: CollectionObject): CollectionIdentifier {
return `${collection.provider}:${String(collection.service)}:${String(collection.identifier)}` as CollectionIdentifier
}
function _isSameMessage(left: EntityObject | null, right: EntityObject): boolean {
if (!left) {
return false
}
return (
left.provider === right.provider &&
String(left.service) === String(right.service) &&
String(left.collection) === String(right.collection) &&
String(left.identifier) === String(right.identifier)
)
}
// ── Actions ───────────────────────────────────────────────────────────────
async function selectFolder(folder: CollectionObject) {
@@ -227,7 +251,7 @@ export const useMailStore = defineStore('mailStore', () => {
_updateSyncSources()
}
function selectMessage(message: EntityInterface<MessageInterface>, closeSidebar = false) {
function selectMessage(message: EntityObject, closeSidebar = false) {
selectedMessage.value = message
composeMode.value = false
@@ -236,12 +260,22 @@ export const useMailStore = defineStore('mailStore', () => {
}
}
function openCompose(replyTo?: EntityInterface<MessageInterface>) {
function openCompose(replyTo?: EntityObject) {
composeMode.value = true
composeReplyTo.value = replyTo ?? null
selectedMessage.value = null
}
function openMoveDialog(message: EntityObject) {
moveMessageCandidate.value = message
moveDialogVisible.value = true
}
function closeMoveDialog() {
moveDialogVisible.value = false
moveMessageCandidate.value = null
}
function closeCompose() {
composeMode.value = false
composeReplyTo.value = null
@@ -257,11 +291,64 @@ export const useMailStore = defineStore('mailStore', () => {
}
}
async function deleteMessage(message: EntityInterface<MessageInterface>) {
async function deleteMessage(message: EntityObject) {
// TODO: implement delete via entity / collection store
console.log('[Mail] Delete message:', message.identifier)
}
async function moveMessage(targetFolder: CollectionObject) {
const message = moveMessageCandidate.value
if (!message) {
return
}
const isSameCollection =
targetFolder.provider === message.provider &&
String(targetFolder.service) === String(message.service) &&
String(targetFolder.identifier) === String(message.collection)
if (isSameCollection) {
closeMoveDialog()
return
}
loading.value = true
try {
const sourceIdentifier = _entityIdentifier(message)
const response = await entitiesStore.move(_collectionIdentifier(targetFolder), [sourceIdentifier])
const result = response[sourceIdentifier]
if (!result || !result.success) {
throw new Error(result && 'error' in result ? result.error : 'Failed to move message')
}
if (_isSameMessage(selectedMessage.value, message)) {
selectedMessage.value = null
}
const service = servicesStore.services.find(entry =>
entry.provider === message.provider &&
String(entry.identifier) === String(message.service),
)
if (service) {
void loadFoldersForService(service)
}
notify(`Message moved to "${targetFolder.properties.label || targetFolder.identifier}"`, 'success')
closeMoveDialog()
} catch (error) {
const messageText = error instanceof Error ? error.message : 'Failed to move message'
console.error('[Mail] Failed to move message:', error)
notify(messageText, 'error')
throw error
} finally {
loading.value = false
}
}
function toggleSidebar() {
sidebarVisible.value = !sidebarVisible.value
}
@@ -291,6 +378,8 @@ export const useMailStore = defineStore('mailStore', () => {
selectedMessage,
composeMode,
composeReplyTo,
moveDialogVisible,
moveMessageCandidate,
serviceFolderLoadingState,
serviceFolderLoadedState,
serviceFolderErrorState,
@@ -302,9 +391,12 @@ export const useMailStore = defineStore('mailStore', () => {
selectFolder,
selectMessage,
openCompose,
openMoveDialog,
closeMoveDialog,
closeCompose,
afterSent,
deleteMessage,
moveMessage,
toggleSidebar,
openSettings,
notify,