diff --git a/src/components/FolderPageView.vue b/src/components/FolderPageView.vue
index 03b04bd..891393b 100644
--- a/src/components/FolderPageView.vue
+++ b/src/components/FolderPageView.vue
@@ -23,6 +23,7 @@ const emit = defineEmits<{
select: [folder: CollectionObject]
createFolder: [service: ServiceObject, parentFolder: CollectionObject | null]
editFolder: [service: ServiceObject, folder: CollectionObject]
+ moveFolder: [service: ServiceObject, folder: CollectionObject]
deleteFolder: [service: ServiceObject, folder: CollectionObject]
}>()
@@ -43,6 +44,10 @@ const getCurrentPageLevel = (service: ServiceObject): (string | number | null)[]
// Get folders for current page level
const getCurrentPageFolders = (service: ServiceObject): CollectionObject[] => {
+ if (service.identifier === null) {
+ return []
+ }
+
const level = getCurrentPageLevel(service)
const currentParent = level[level.length - 1]
return collectionsStore.collectionsInCollection(service.provider, service.identifier, currentParent)
@@ -54,6 +59,10 @@ const hasChildren = (folder: CollectionObject): boolean => {
}
const getServiceFolders = (service: ServiceObject): CollectionObject[] => {
+ if (service.identifier === null) {
+ return []
+ }
+
return collectionsStore.collectionsForService(service.provider, service.identifier)
}
@@ -132,6 +141,10 @@ const navigateBack = (service: ServiceObject) => {
// Get breadcrumb label for current page
const getCurrentBreadcrumb = (service: ServiceObject): string => {
+ if (service.identifier === null) {
+ return 'Folders'
+ }
+
const level = getCurrentPageLevel(service)
const currentParent = level[level.length - 1]
if (currentParent === null) return 'All Folders'
@@ -148,6 +161,10 @@ const getCurrentBreadcrumb = (service: ServiceObject): string => {
// Get current parent folder for dialog context
const getCurrentParentFolder = (service: ServiceObject): CollectionObject | null => {
+ if (service.identifier === null) {
+ return null
+ }
+
const level = getCurrentPageLevel(service)
const currentParent = level[level.length - 1]
if (currentParent === null) return null
@@ -276,6 +293,12 @@ const getCurrentParentFolder = (service: ServiceObject): CollectionObject | null
>
New Subfolder
+
+ Move Folder
+
New Subfolder
+
+ Move Folder
+
import { computed, ref, watch } from 'vue'
import { useCollectionsStore } from '@MailManager/stores/collectionsStore'
-import { useServicesStore } from '@MailManager/stores/servicesStore'
import { useMailStore } from '@/stores/mailStore'
import type { ServiceObject, CollectionObject } from '@MailManager/models'
import FolderSelectionTreeNode from './FolderSelectionTreeNode.vue'
@@ -12,6 +11,8 @@ interface Props {
title?: string
confirmText?: string
emptyText?: string
+ service?: ServiceObject | null
+ disabledFolderKeys?: string[]
}
const props = withDefaults(defineProps(), {
@@ -19,6 +20,8 @@ const props = withDefaults(defineProps(), {
title: 'Move To',
confirmText: 'Move',
emptyText: 'No folders are available.',
+ service: null,
+ disabledFolderKeys: () => [],
})
const emit = defineEmits<{
@@ -28,7 +31,6 @@ const emit = defineEmits<{
}>()
const collectionsStore = useCollectionsStore()
-const servicesStore = useServicesStore()
const mailStore = useMailStore()
const selectedFolderKey = ref(null)
@@ -50,13 +52,7 @@ interface ServiceGroup {
}
const serviceGroups = computed(() => {
- const context = mailStore.moveDialogService
-
- if (!context) {
- return []
- }
-
- const service = servicesStore.serviceByIdentifier(mailStore.moveDialogService)
+ const service = props.service ?? mailStore.moveDialogService
if (!service) {
return []
@@ -173,6 +169,7 @@ const handleConfirm = () => {
:folder="folder"
:service="group.service"
:selected-folder-key="selectedFolderKey"
+ :disabled-folder-keys="disabledFolderKeys"
@select="handleSelect"
/>
diff --git a/src/components/FolderSelectionTreeNode.vue b/src/components/FolderSelectionTreeNode.vue
index 0a0aeb7..799d37e 100644
--- a/src/components/FolderSelectionTreeNode.vue
+++ b/src/components/FolderSelectionTreeNode.vue
@@ -8,6 +8,7 @@ interface Props {
folder: CollectionObject
service: ServiceObject
selectedFolderKey: string | null
+ disabledFolderKeys?: string[]
}
const props = defineProps()
@@ -66,6 +67,7 @@ const folderColorFor = (folder: CollectionObject): string | undefined => {
}
const key = computed(() => folderKeyFor(props.folder))
+const isDisabled = computed(() => (props.disabledFolderKeys ?? []).includes(key.value))
const childFolders = computed(() => {
const serviceIdentifier = props.service.identifier
@@ -89,6 +91,10 @@ const hasChildren = computed(() => {
const isSelected = computed(() => props.selectedFolderKey === key.value)
const onSelect = () => {
+ if (isDisabled.value) {
+ return
+ }
+
emit('select', props.folder)
}
@@ -111,6 +117,7 @@ const onGroupDoubleClick = () => {
class="folder-node"
:title="folderLabelFor(folder)"
:active="isSelected"
+ :disabled="isDisabled"
@click="onSelect"
@dblclick.stop="onGroupDoubleClick"
>
@@ -152,6 +159,7 @@ const onGroupDoubleClick = () => {
class="folder-node"
:title="folderLabelFor(folder)"
:active="isSelected"
+ :disabled="isDisabled"
@click="onSelect"
>
diff --git a/src/components/FolderTree.vue b/src/components/FolderTree.vue
index 4f06212..72ddf07 100644
--- a/src/components/FolderTree.vue
+++ b/src/components/FolderTree.vue
@@ -8,6 +8,7 @@ import FolderTreeView from './FolderTreeView.vue'
import FolderPageView from './FolderPageView.vue'
import CreateFolderDialog from './CreateFolderDialog.vue'
import DeleteFolderDialog from './DeleteFolderDialog.vue'
+import FolderSelectionDialog from './FolderSelectionDialog.vue'
import RenameFolderDialog from './RenameFolderDialog.vue'
import type { CollectionObject } from '@MailManager/models/collection'
import type { ServiceObject } from '@MailManager/models'
@@ -44,6 +45,9 @@ const createDialogParent = ref(null)
const renameDialogVisible = ref(false)
const renameDialogService = ref(null)
const renameDialogFolder = ref(null)
+const moveDialogVisible = ref(false)
+const moveDialogService = ref(null)
+const moveDialogFolder = ref(null)
const deleteDialogVisible = ref(false)
const deleteDialogService = ref(null)
const deleteDialogFolder = ref(null)
@@ -67,6 +71,12 @@ const handleEditFolder = (service: ServiceObject, folder: CollectionObject) => {
renameDialogVisible.value = true
}
+const handleMoveFolder = (service: ServiceObject, folder: CollectionObject) => {
+ moveDialogService.value = service
+ moveDialogFolder.value = folder
+ moveDialogVisible.value = true
+}
+
const handleDeleteFolder = (service: ServiceObject, folder: CollectionObject) => {
deleteDialogService.value = service
deleteDialogFolder.value = folder
@@ -77,6 +87,39 @@ const handleFolderRenamed = (updatedFolder: CollectionObject) => {
emit('select', updatedFolder)
}
+const folderKeyFor = (folder: CollectionObject): string => {
+ return `${folder.provider}:${String(folder.service)}:${String(folder.identifier)}`
+}
+
+const moveDialogInvalidFolderKeys = computed(() => {
+ const sourceFolder = moveDialogFolder.value
+
+ if (!sourceFolder) {
+ return []
+ }
+
+ const invalidKeys = new Set()
+ const queue: CollectionObject[] = [sourceFolder]
+
+ while (queue.length > 0) {
+ const currentFolder = queue.shift()
+
+ if (!currentFolder) {
+ continue
+ }
+
+ invalidKeys.add(folderKeyFor(currentFolder))
+
+ collectionsStore
+ .collectionsInCollection(currentFolder.provider, currentFolder.service, currentFolder.identifier)
+ .forEach(childFolder => {
+ queue.push(childFolder)
+ })
+ }
+
+ return Array.from(invalidKeys)
+})
+
const isSameFolder = (left: CollectionObject | null | undefined, right: CollectionObject | null | undefined) => {
if (!left || !right) {
return false
@@ -95,6 +138,35 @@ const handleFolderDeleted = (deletedFolder: CollectionObject) => {
mailStore.notify(`Folder "${deletedFolder.properties.label || String(deletedFolder.identifier)}" deleted`, 'success')
}
+const handleMoveDialogCancel = () => {
+ moveDialogVisible.value = false
+}
+
+const handleFolderMove = async (targetFolder: CollectionObject) => {
+ const sourceFolder = moveDialogFolder.value
+
+ if (!sourceFolder) {
+ return
+ }
+
+ try {
+ const movedFolder = await collectionsStore.move(folderKeyFor(targetFolder), folderKeyFor(sourceFolder))
+ moveDialogVisible.value = false
+
+ if (isSameFolder(props.selectedFolder, sourceFolder)) {
+ emit('select', movedFolder)
+ }
+
+ mailStore.notify(
+ `Folder "${sourceFolder.properties.label || String(sourceFolder.identifier)}" moved to "${targetFolder.properties.label || String(targetFolder.identifier)}"`,
+ 'success',
+ )
+ } catch (error: unknown) {
+ console.error('[FolderTree] Failed to move folder:', error)
+ mailStore.notify(error instanceof Error ? error.message : 'Failed to move folder', 'error')
+ }
+}
+
// Computed: all folders for validation
const allFolders = computed(() =>
servicesStore.servicesEnabled.flatMap(service =>
@@ -134,6 +206,7 @@ const serviceGroups = computed(() => {
@select="emit('select', $event)"
@create-folder="handleCreateFolder"
@edit-folder="handleEditFolder"
+ @move-folder="handleMoveFolder"
@delete-folder="handleDeleteFolder"
/>
@@ -145,6 +218,7 @@ const serviceGroups = computed(() => {
@select="emit('select', $event)"
@create-folder="handleCreateFolder"
@edit-folder="handleEditFolder"
+ @move-folder="handleMoveFolder"
@delete-folder="handleDeleteFolder"
/>
@@ -175,6 +249,19 @@ const serviceGroups = computed(() => {
@updated="handleFolderRenamed"
/>
+
+
()
@@ -140,6 +141,12 @@ const isSelected = (folder: CollectionObject): boolean => {
>
New Subfolder
+
+ Move Folder
+
{
@select="emit('select', $event)"
@create-subfolder="(service, parentFolder) => emit('createSubfolder', service, parentFolder)"
@edit-folder="(service, folder) => emit('editFolder', service, folder)"
+ @move-folder="(service, folder) => emit('moveFolder', service, folder)"
@delete-folder="(service, folder) => emit('deleteFolder', service, folder)"
/>
@@ -219,6 +227,12 @@ const isSelected = (folder: CollectionObject): boolean => {
>
New Subfolder
+
+ Move Folder
+
}
-const props = defineProps()
+defineProps()
const collectionsStore = useCollectionsStore()
// Emits
@@ -23,6 +23,7 @@ const emit = defineEmits<{
select: [folder: CollectionObject]
createFolder: [service: ServiceObject, parentFolder: CollectionObject | null]
editFolder: [service: ServiceObject, folder: CollectionObject]
+ moveFolder: [service: ServiceObject, folder: CollectionObject]
deleteFolder: [service: ServiceObject, folder: CollectionObject]
}>()
@@ -75,6 +76,7 @@ const getServiceFolders = (service: ServiceObject): CollectionObject[] => {
@select="emit('select', $event)"
@create-subfolder="(service, parentFolder) => emit('createFolder', service, parentFolder)"
@edit-folder="(service, folder) => emit('editFolder', service, folder)"
+ @move-folder="(service, folder) => emit('moveFolder', service, folder)"
@delete-folder="(service, folder) => emit('deleteFolder', service, folder)"
/>
@@ -157,6 +159,7 @@ const getServiceFolders = (service: ServiceObject): CollectionObject[] => {
@select="emit('select', $event)"
@create-subfolder="(service, parentFolder) => emit('createFolder', service, parentFolder)"
@edit-folder="(service, folder) => emit('editFolder', service, folder)"
+ @move-folder="(service, folder) => emit('moveFolder', service, folder)"
@delete-folder="(service, folder) => emit('deleteFolder', service, folder)"
/>