@@ -3,12 +3,12 @@ import { ref, computed, watch } from 'vue'
|
||||
import { useCollectionsStore } from '@MailManager/stores/collectionsStore'
|
||||
import { CollectionPropertiesObject } from '@MailManager/models/collection'
|
||||
import type { CollectionObject } from '@MailManager/models/collection'
|
||||
import type { ServiceInterface } from '@MailManager/types/service'
|
||||
import type { ServiceObject } from '@MailManager/models'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
service: ServiceInterface
|
||||
service: ServiceObject
|
||||
parentFolder?: CollectionObject | null
|
||||
allFolders?: CollectionObject[]
|
||||
}
|
||||
|
||||
@@ -1,34 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useCollectionsStore } from '@MailManager/stores/collectionsStore'
|
||||
import type { CollectionObject } from '@MailManager/models/collection'
|
||||
import type { ServiceInterface } from '@MailManager/types/service'
|
||||
import type { ServiceObject } from '@MailManager/models'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
selectedFolder?: CollectionObject | null
|
||||
serviceGroups: Array<{
|
||||
service: ServiceInterface
|
||||
folders: CollectionObject[]
|
||||
service: ServiceObject
|
||||
loading: boolean
|
||||
loaded: boolean
|
||||
error: string | null
|
||||
}>
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const collectionsStore = useCollectionsStore()
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
select: [folder: CollectionObject]
|
||||
createFolder: [service: ServiceInterface, parentFolder: CollectionObject | null]
|
||||
editFolder: [service: ServiceInterface, folder: CollectionObject]
|
||||
createFolder: [service: ServiceObject, parentFolder: CollectionObject | null]
|
||||
editFolder: [service: ServiceObject, folder: CollectionObject]
|
||||
}>()
|
||||
|
||||
// Page-based navigation state per service account
|
||||
const pageLevels = ref<Record<string, (string | number | null)[]>>({})
|
||||
|
||||
const getServiceKey = (service: ServiceInterface): string => {
|
||||
const getServiceKey = (service: ServiceObject): string => {
|
||||
return `${service.provider}-${service.identifier}`
|
||||
}
|
||||
|
||||
const getCurrentPageLevel = (service: ServiceInterface): (string | number | null)[] => {
|
||||
const getCurrentPageLevel = (service: ServiceObject): (string | number | null)[] => {
|
||||
const key = getServiceKey(service)
|
||||
if (!pageLevels.value[key]) {
|
||||
pageLevels.value[key] = [null]
|
||||
@@ -37,20 +41,19 @@ const getCurrentPageLevel = (service: ServiceInterface): (string | number | null
|
||||
}
|
||||
|
||||
// Get folders for current page level
|
||||
const getCurrentPageFolders = (service: ServiceInterface, folders: CollectionObject[]): CollectionObject[] => {
|
||||
const getCurrentPageFolders = (service: ServiceObject): CollectionObject[] => {
|
||||
const level = getCurrentPageLevel(service)
|
||||
const currentParent = level[level.length - 1]
|
||||
return folders.filter(f => {
|
||||
if (currentParent === null) {
|
||||
return f.collection === null || f.collection === undefined
|
||||
}
|
||||
return String(f.collection) === String(currentParent)
|
||||
})
|
||||
return collectionsStore.collectionsInCollection(service.provider, service.identifier, currentParent)
|
||||
}
|
||||
|
||||
// Check if folder has children
|
||||
const hasChildren = (folder: CollectionObject, allFolders: CollectionObject[]): boolean => {
|
||||
return allFolders.some(f => String(f.collection) === String(folder.identifier))
|
||||
const hasChildren = (folder: CollectionObject): boolean => {
|
||||
return collectionsStore.hasChildrenInCollection(folder.provider, folder.service, folder.identifier)
|
||||
}
|
||||
|
||||
const getServiceFolders = (service: ServiceObject): CollectionObject[] => {
|
||||
return collectionsStore.collectionsForService(service.provider, service.identifier)
|
||||
}
|
||||
|
||||
// Get icon for folder based on role
|
||||
@@ -109,13 +112,13 @@ const handleFolderClick = (folder: CollectionObject) => {
|
||||
}
|
||||
|
||||
// Navigate into a folder to show its children
|
||||
const handleNavigateInto = (service: ServiceInterface, folderId: string | number) => {
|
||||
const handleNavigateInto = (service: ServiceObject, folderId: string | number) => {
|
||||
const level = getCurrentPageLevel(service)
|
||||
level.push(folderId)
|
||||
}
|
||||
|
||||
// Navigate back in page-based view
|
||||
const navigateBack = (service: ServiceInterface) => {
|
||||
const navigateBack = (service: ServiceObject) => {
|
||||
const level = getCurrentPageLevel(service)
|
||||
if (level.length > 1) {
|
||||
level.pop()
|
||||
@@ -123,14 +126,14 @@ const navigateBack = (service: ServiceInterface) => {
|
||||
}
|
||||
|
||||
// Get breadcrumb label for current page
|
||||
const getCurrentBreadcrumb = (service: ServiceInterface, folders: CollectionObject[]): string => {
|
||||
const getCurrentBreadcrumb = (service: ServiceObject): string => {
|
||||
const level = getCurrentPageLevel(service)
|
||||
const currentParent = level[level.length - 1]
|
||||
if (currentParent === null) return 'All Folders'
|
||||
|
||||
const labels = level
|
||||
.filter((id): id is string | number => id !== null)
|
||||
.map(id => folders.find(f => String(f.identifier) === String(id))?.properties.label)
|
||||
.map(id => collectionsStore.collection(service.provider, service.identifier, id)?.properties.label)
|
||||
.filter((label): label is string => !!label)
|
||||
|
||||
if (labels.length === 0) return 'Folders'
|
||||
@@ -139,14 +142,12 @@ const getCurrentBreadcrumb = (service: ServiceInterface, folders: CollectionObje
|
||||
}
|
||||
|
||||
// Get current parent folder for dialog context
|
||||
const getCurrentParentFolder = (service: ServiceInterface, folders: CollectionObject[]): CollectionObject | null => {
|
||||
const getCurrentParentFolder = (service: ServiceObject): CollectionObject | null => {
|
||||
const level = getCurrentPageLevel(service)
|
||||
const currentParent = level[level.length - 1]
|
||||
if (currentParent === null) return null
|
||||
|
||||
// Search through all folders in the array
|
||||
const found = folders.find(f => String(f.identifier) === String(currentParent))
|
||||
return found || null
|
||||
|
||||
return collectionsStore.collection(service.provider, service.identifier, currentParent)
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -176,7 +177,7 @@ const getCurrentParentFolder = (service: ServiceInterface, folders: CollectionOb
|
||||
variant="text"
|
||||
size="small"
|
||||
density="compact"
|
||||
@click.stop="emit('createFolder', group.service, getCurrentParentFolder(group.service, group.folders))"
|
||||
@click.stop="emit('createFolder', group.service, getCurrentParentFolder(group.service))"
|
||||
>
|
||||
<v-icon>mdi-folder-plus</v-icon>
|
||||
<v-tooltip activator="parent" location="bottom">New Folder</v-tooltip>
|
||||
@@ -187,12 +188,12 @@ const getCurrentParentFolder = (service: ServiceInterface, folders: CollectionOb
|
||||
|
||||
<!-- Breadcrumb with New Folder button -->
|
||||
<v-list-subheader v-if="getCurrentPageLevel(group.service).length > 1" class="d-flex align-center">
|
||||
<span class="flex-grow-1">{{ getCurrentBreadcrumb(group.service, group.folders) }}</span>
|
||||
<span class="flex-grow-1">{{ getCurrentBreadcrumb(group.service) }}</span>
|
||||
<v-btn
|
||||
icon="mdi-folder-plus"
|
||||
variant="text"
|
||||
size="x-small"
|
||||
@click="emit('createFolder', group.service, getCurrentParentFolder(group.service, group.folders))"
|
||||
@click="emit('createFolder', group.service, getCurrentParentFolder(group.service))"
|
||||
>
|
||||
<v-icon size="small">mdi-folder-plus</v-icon>
|
||||
<v-tooltip activator="parent" location="bottom">New Subfolder</v-tooltip>
|
||||
@@ -210,7 +211,7 @@ const getCurrentParentFolder = (service: ServiceInterface, folders: CollectionOb
|
||||
|
||||
<!-- Current level folders -->
|
||||
<v-list-item
|
||||
v-for="folder in getCurrentPageFolders(group.service, group.folders)"
|
||||
v-for="folder in getCurrentPageFolders(group.service)"
|
||||
:key="`${folder.provider}-${folder.service}-${folder.identifier}`"
|
||||
class="folder-page-item folder-row-item"
|
||||
:title="folder.properties.label"
|
||||
@@ -236,7 +237,7 @@ const getCurrentParentFolder = (service: ServiceInterface, folders: CollectionOb
|
||||
|
||||
<!-- Chevron for folders with children -->
|
||||
<v-btn
|
||||
v-if="hasChildren(folder, group.folders)"
|
||||
v-if="hasChildren(folder)"
|
||||
icon="mdi-chevron-right"
|
||||
variant="text"
|
||||
size="small"
|
||||
@@ -274,20 +275,73 @@ const getCurrentParentFolder = (service: ServiceInterface, folders: CollectionOb
|
||||
</v-menu>
|
||||
</template>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item v-if="group.loading && getServiceFolders(group.service).length === 0" disabled class="folder-status-item">
|
||||
<template v-slot:prepend>
|
||||
<v-progress-circular indeterminate size="18" width="2" color="primary" />
|
||||
</template>
|
||||
<v-list-item-title>Loading folders</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item
|
||||
v-else-if="group.error && getServiceFolders(group.service).length === 0"
|
||||
disabled
|
||||
class="folder-status-item"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-alert-circle-outline" color="error" />
|
||||
</template>
|
||||
<v-list-item-title>Folders unavailable</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ group.error }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item
|
||||
v-else-if="group.loaded && getServiceFolders(group.service).length === 0"
|
||||
disabled
|
||||
class="folder-status-item"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-folder-off-outline" />
|
||||
</template>
|
||||
<v-list-item-title>No folders found</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list-group>
|
||||
|
||||
<!-- Single service - show folders directly -->
|
||||
<template v-else>
|
||||
<v-list-item
|
||||
class="account-header-item"
|
||||
:title="group.service.label || 'Mail Account'"
|
||||
:subtitle="group.service.primaryAddress || undefined"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-email-outline" />
|
||||
</template>
|
||||
|
||||
<template v-slot:append>
|
||||
<v-btn
|
||||
icon="mdi-folder-plus"
|
||||
variant="text"
|
||||
size="small"
|
||||
density="compact"
|
||||
@click.stop="emit('createFolder', group.service, getCurrentParentFolder(group.service))"
|
||||
>
|
||||
<v-icon>mdi-folder-plus</v-icon>
|
||||
<v-tooltip activator="parent" location="bottom">New Folder</v-tooltip>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-list-item>
|
||||
|
||||
<!-- Header with New Folder button -->
|
||||
<v-list-subheader class="d-flex align-center">
|
||||
<span class="flex-grow-1">
|
||||
{{ getCurrentPageLevel(group.service).length > 1 ? getCurrentBreadcrumb(group.service, group.folders) : 'FOLDERS' }}
|
||||
{{ getCurrentPageLevel(group.service).length > 1 ? getCurrentBreadcrumb(group.service) : 'FOLDERS' }}
|
||||
</span>
|
||||
<v-btn
|
||||
icon="mdi-folder-plus"
|
||||
variant="text"
|
||||
size="x-small"
|
||||
@click="emit('createFolder', group.service, getCurrentParentFolder(group.service, group.folders))"
|
||||
@click="emit('createFolder', group.service, getCurrentParentFolder(group.service))"
|
||||
>
|
||||
<v-icon size="small">mdi-folder-plus</v-icon>
|
||||
<v-tooltip activator="parent" location="bottom">
|
||||
@@ -307,7 +361,7 @@ const getCurrentParentFolder = (service: ServiceInterface, folders: CollectionOb
|
||||
|
||||
<!-- Current level folders -->
|
||||
<v-list-item
|
||||
v-for="folder in getCurrentPageFolders(group.service, group.folders)"
|
||||
v-for="folder in getCurrentPageFolders(group.service)"
|
||||
:key="`${folder.provider}-${folder.service}-${folder.identifier}`"
|
||||
class="folder-row-item"
|
||||
:title="folder.properties.label"
|
||||
@@ -333,7 +387,7 @@ const getCurrentParentFolder = (service: ServiceInterface, folders: CollectionOb
|
||||
|
||||
<!-- Chevron for folders with children or Menu for actions -->
|
||||
<v-btn
|
||||
v-if="hasChildren(folder, group.folders)"
|
||||
v-if="hasChildren(folder)"
|
||||
icon="mdi-chevron-right"
|
||||
variant="text"
|
||||
size="small"
|
||||
@@ -370,11 +424,59 @@ const getCurrentParentFolder = (service: ServiceInterface, folders: CollectionOb
|
||||
</v-menu>
|
||||
</template>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item v-if="group.loading && getServiceFolders(group.service).length === 0" disabled class="folder-status-item">
|
||||
<template v-slot:prepend>
|
||||
<v-progress-circular indeterminate size="18" width="2" color="primary" />
|
||||
</template>
|
||||
<v-list-item-title>Loading folders</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item
|
||||
v-else-if="group.error && getServiceFolders(group.service).length === 0"
|
||||
disabled
|
||||
class="folder-status-item"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-alert-circle-outline" color="error" />
|
||||
</template>
|
||||
<v-list-item-title>Folders unavailable</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ group.error }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item
|
||||
v-else-if="group.loaded && getServiceFolders(group.service).length === 0"
|
||||
disabled
|
||||
class="folder-status-item"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-folder-off-outline" />
|
||||
</template>
|
||||
<v-list-item-title>No folders found</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.account-header-item {
|
||||
--v-list-item-prepend-size: 22px;
|
||||
background-color: rgba(var(--v-theme-primary), 0.1);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.account-header-item :deep(.v-list-item__prepend) {
|
||||
padding-inline-start: 4px;
|
||||
margin-inline-end: 2px;
|
||||
}
|
||||
|
||||
.folder-status-item {
|
||||
padding-inline-start: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.v-list-item--active {
|
||||
background-color: rgba(var(--v-theme-primary), 0.12);
|
||||
|
||||
@@ -2,22 +2,20 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { useCollectionsStore } from '@MailManager/stores/collectionsStore'
|
||||
import { useServicesStore } from '@MailManager/stores/servicesStore'
|
||||
import { useMailStore } from '@/stores/mailStore'
|
||||
import { useUser } from '@KTXC'
|
||||
import FolderTreeView from './FolderTreeView.vue'
|
||||
import FolderPageView from './FolderPageView.vue'
|
||||
import CreateFolderDialog from './CreateFolderDialog.vue'
|
||||
import RenameFolderDialog from './RenameFolderDialog.vue'
|
||||
import type { CollectionObject } from '@MailManager/models/collection'
|
||||
import type { ServiceInterface } from '@MailManager/types/service'
|
||||
import type { ServiceObject } from '@MailManager/models'
|
||||
|
||||
type FolderViewMode = 'tree' | 'page'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
const props = defineProps<{
|
||||
selectedFolder?: CollectionObject | null
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
}>()
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
@@ -28,6 +26,7 @@ const emit = defineEmits<{
|
||||
// Stores
|
||||
const collectionsStore = useCollectionsStore()
|
||||
const servicesStore = useServicesStore()
|
||||
const mailStore = useMailStore()
|
||||
|
||||
// User settings
|
||||
const { settings } = useUser()
|
||||
@@ -39,14 +38,14 @@ const folderViewMode = computed(() => {
|
||||
|
||||
// Create folder dialog state
|
||||
const createDialogVisible = ref(false)
|
||||
const createDialogService = ref<ServiceInterface | null>(null)
|
||||
const createDialogService = ref<ServiceObject | null>(null)
|
||||
const createDialogParent = ref<CollectionObject | null>(null)
|
||||
const renameDialogVisible = ref(false)
|
||||
const renameDialogService = ref<ServiceInterface | null>(null)
|
||||
const renameDialogService = ref<ServiceObject | null>(null)
|
||||
const renameDialogFolder = ref<CollectionObject | null>(null)
|
||||
|
||||
// Handle create folder event from child components
|
||||
const handleCreateFolder = (service: ServiceInterface, parentFolder: CollectionObject | null = null) => {
|
||||
const handleCreateFolder = (service: ServiceObject, parentFolder: CollectionObject | null = null) => {
|
||||
createDialogService.value = service
|
||||
createDialogParent.value = parentFolder
|
||||
createDialogVisible.value = true
|
||||
@@ -58,7 +57,7 @@ const handleFolderCreated = (newFolder: CollectionObject) => {
|
||||
emit('select', newFolder)
|
||||
}
|
||||
|
||||
const handleEditFolder = (service: ServiceInterface, folder: CollectionObject) => {
|
||||
const handleEditFolder = (service: ServiceObject, folder: CollectionObject) => {
|
||||
renameDialogService.value = service
|
||||
renameDialogFolder.value = folder
|
||||
renameDialogVisible.value = true
|
||||
@@ -70,71 +69,27 @@ const handleFolderRenamed = (updatedFolder: CollectionObject) => {
|
||||
|
||||
// Computed: all folders for validation
|
||||
const allFolders = computed(() =>
|
||||
serviceGroups.value.flatMap(g => g.folders)
|
||||
servicesStore.services.flatMap(service =>
|
||||
collectionsStore.collectionsForService(service.provider, service.identifier),
|
||||
)
|
||||
)
|
||||
|
||||
// Folder hierarchy helper type
|
||||
interface FolderNode {
|
||||
folder: CollectionObject
|
||||
children: FolderNode[]
|
||||
interface ServiceGroup {
|
||||
service: ServiceObject
|
||||
loading: boolean
|
||||
loaded: boolean
|
||||
error: string | null
|
||||
}
|
||||
|
||||
// Build hierarchical tree structure
|
||||
const buildFolderTree = (folders: CollectionObject[]): FolderNode[] => {
|
||||
const nodeMap = new Map<string | number, FolderNode>()
|
||||
const roots: FolderNode[] = []
|
||||
|
||||
// Create nodes for all folders
|
||||
folders.forEach(folder => {
|
||||
nodeMap.set(folder.identifier, {
|
||||
folder,
|
||||
children: []
|
||||
})
|
||||
})
|
||||
|
||||
// Build parent-child relationships
|
||||
folders.forEach(folder => {
|
||||
const node = nodeMap.get(folder.identifier)
|
||||
if (!node) return
|
||||
|
||||
if (folder.collection === null || folder.collection === undefined) {
|
||||
// Root level folder
|
||||
roots.push(node)
|
||||
} else {
|
||||
// Child folder - add to parent
|
||||
const parent = nodeMap.get(folder.collection)
|
||||
if (parent) {
|
||||
parent.children.push(node)
|
||||
} else {
|
||||
// Parent not found, treat as root
|
||||
roots.push(node)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return roots
|
||||
}
|
||||
|
||||
// Group collections by service
|
||||
const serviceGroups = computed(() => {
|
||||
const groups: Array<{
|
||||
service: ServiceInterface
|
||||
folders: CollectionObject[]
|
||||
folderTree: FolderNode[]
|
||||
}> = []
|
||||
const groups: ServiceGroup[] = []
|
||||
|
||||
servicesStore.services.forEach(service => {
|
||||
const folders = collectionsStore.collections.filter(
|
||||
c => c.provider === service.provider && String(c.service) === String(service.identifier)
|
||||
)
|
||||
|
||||
if (folders.length > 0) {
|
||||
groups.push({
|
||||
service,
|
||||
folders,
|
||||
folderTree: buildFolderTree(folders)
|
||||
})
|
||||
}
|
||||
groups.push({
|
||||
service,
|
||||
loading: mailStore.isServiceFolderLoading(service.provider, service.identifier),
|
||||
loaded: mailStore.hasServiceFoldersLoaded(service.provider, service.identifier),
|
||||
error: mailStore.getServiceFolderError(service.provider, service.identifier),
|
||||
})
|
||||
})
|
||||
|
||||
return groups
|
||||
@@ -164,7 +119,7 @@ const serviceGroups = computed(() => {
|
||||
/>
|
||||
|
||||
<!-- Empty state -->
|
||||
<v-list-item v-if="serviceGroups.length === 0">
|
||||
<v-list-item v-if="servicesStore.services.length === 0">
|
||||
<v-list-item-title class="text-center text-medium-emphasis">
|
||||
No mail accounts configured
|
||||
</v-list-item-title>
|
||||
|
||||
@@ -1,26 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useCollectionsStore } from '@MailManager/stores/collectionsStore'
|
||||
import type { CollectionObject } from '@MailManager/models/collection'
|
||||
import type { ServiceInterface } from '@MailManager/types/service'
|
||||
|
||||
export interface FolderNode {
|
||||
folder: CollectionObject
|
||||
children: FolderNode[]
|
||||
}
|
||||
import type { ServiceObject } from '@MailManager/models'
|
||||
|
||||
export interface Props {
|
||||
node: FolderNode
|
||||
service: ServiceInterface
|
||||
folder: CollectionObject
|
||||
service: ServiceObject
|
||||
selectedFolder?: CollectionObject | null
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const collectionsStore = useCollectionsStore()
|
||||
const expanded = ref(false)
|
||||
|
||||
const emit = defineEmits<{
|
||||
select: [folder: CollectionObject]
|
||||
createSubfolder: [service: ServiceInterface, parentFolder: CollectionObject]
|
||||
editFolder: [service: ServiceInterface, folder: CollectionObject]
|
||||
createSubfolder: [service: ServiceObject, parentFolder: CollectionObject]
|
||||
editFolder: [service: ServiceObject, folder: CollectionObject]
|
||||
}>()
|
||||
|
||||
const childFolders = computed(() => {
|
||||
return collectionsStore.collectionsInCollection(props.service.provider, props.service.identifier, props.folder.identifier)
|
||||
})
|
||||
|
||||
const hasChildren = computed(() => {
|
||||
return collectionsStore.hasChildrenInCollection(props.service.provider, props.service.identifier, props.folder.identifier)
|
||||
})
|
||||
|
||||
const handleExpandableFolderClick = () => {
|
||||
expanded.value = !expanded.value
|
||||
emit('select', props.folder)
|
||||
}
|
||||
|
||||
// Get icon for folder based on role
|
||||
const getFolderIcon = (folder: CollectionObject): string => {
|
||||
switch (folder.properties.role) {
|
||||
@@ -73,26 +85,26 @@ const isSelected = (folder: CollectionObject): boolean => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-list-group v-if="node.children.length > 0" class="folder-tree-group">
|
||||
<v-list-group v-if="hasChildren" v-model="expanded" class="folder-tree-group">
|
||||
<template v-slot:activator="{ props: activatorProps }">
|
||||
<v-list-item
|
||||
v-bind="activatorProps"
|
||||
class="folder-row-item"
|
||||
:title="node.folder.properties.label"
|
||||
:active="isSelected(node.folder)"
|
||||
@click.stop="emit('select', node.folder)"
|
||||
:title="folder.properties.label"
|
||||
:active="isSelected(folder)"
|
||||
@click.stop="handleExpandableFolderClick()"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon
|
||||
:icon="getFolderIcon(node.folder)"
|
||||
:color="getFolderColor(node.folder)"
|
||||
:icon="getFolderIcon(folder)"
|
||||
:color="getFolderColor(folder)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-slot:append>
|
||||
<v-badge
|
||||
v-if="node.folder.properties.unread && node.folder.properties.unread > 0"
|
||||
:content="node.folder.properties.unread"
|
||||
v-if="folder.properties.unread && folder.properties.unread > 0"
|
||||
:content="folder.properties.unread"
|
||||
color="primary"
|
||||
inline
|
||||
class="mr-2"
|
||||
@@ -116,13 +128,13 @@ const isSelected = (folder: CollectionObject): boolean => {
|
||||
<v-list density="compact">
|
||||
<v-list-item
|
||||
prepend-icon="mdi-pencil"
|
||||
@click="emit('editFolder', service, node.folder)"
|
||||
@click="emit('editFolder', service, folder)"
|
||||
>
|
||||
<v-list-item-title>Edit Folder Name</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
prepend-icon="mdi-folder-plus"
|
||||
@click="emit('createSubfolder', service, node.folder)"
|
||||
@click="emit('createSubfolder', service, folder)"
|
||||
>
|
||||
<v-list-item-title>New Subfolder</v-list-item-title>
|
||||
</v-list-item>
|
||||
@@ -132,11 +144,11 @@ const isSelected = (folder: CollectionObject): boolean => {
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<div class="folder-tree-children">
|
||||
<div v-if="expanded" class="folder-tree-children">
|
||||
<FolderTreeNode
|
||||
v-for="child in node.children"
|
||||
:key="child.folder.identifier"
|
||||
:node="child"
|
||||
v-for="childFolder in childFolders"
|
||||
:key="childFolder.identifier"
|
||||
:folder="childFolder"
|
||||
:service="service"
|
||||
:selected-folder="selectedFolder"
|
||||
@select="emit('select', $event)"
|
||||
@@ -149,21 +161,21 @@ const isSelected = (folder: CollectionObject): boolean => {
|
||||
<v-list-item
|
||||
v-else
|
||||
class="folder-tree-item folder-row-item"
|
||||
:title="node.folder.properties.label"
|
||||
:active="isSelected(node.folder)"
|
||||
@click="emit('select', node.folder)"
|
||||
:title="folder.properties.label"
|
||||
:active="isSelected(folder)"
|
||||
@click="emit('select', folder)"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon
|
||||
:icon="getFolderIcon(node.folder)"
|
||||
:color="getFolderColor(node.folder)"
|
||||
:icon="getFolderIcon(folder)"
|
||||
:color="getFolderColor(folder)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-slot:append>
|
||||
<v-badge
|
||||
v-if="node.folder.properties.unread && node.folder.properties.unread > 0"
|
||||
:content="node.folder.properties.unread"
|
||||
v-if="folder.properties.unread && folder.properties.unread > 0"
|
||||
:content="folder.properties.unread"
|
||||
color="primary"
|
||||
inline
|
||||
class="mr-2"
|
||||
@@ -187,13 +199,13 @@ const isSelected = (folder: CollectionObject): boolean => {
|
||||
<v-list density="compact">
|
||||
<v-list-item
|
||||
prepend-icon="mdi-pencil"
|
||||
@click="emit('editFolder', service, node.folder)"
|
||||
@click="emit('editFolder', service, folder)"
|
||||
>
|
||||
<v-list-item-title>Edit Folder Name</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
prepend-icon="mdi-folder-plus"
|
||||
@click="emit('createSubfolder', service, node.folder)"
|
||||
@click="emit('createSubfolder', service, folder)"
|
||||
>
|
||||
<v-list-item-title>New Subfolder</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
@@ -1,32 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import { useCollectionsStore } from '@MailManager/stores/collectionsStore'
|
||||
import type { CollectionObject } from '@MailManager/models/collection'
|
||||
import type { ServiceInterface } from '@MailManager/types/service'
|
||||
import type { ServiceObject } from '@MailManager/models'
|
||||
import FolderTreeNode from './FolderTreeNode.vue'
|
||||
|
||||
// Folder hierarchy helper type
|
||||
export interface FolderNode {
|
||||
folder: CollectionObject
|
||||
children: FolderNode[]
|
||||
}
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
selectedFolder?: CollectionObject | null
|
||||
serviceGroups: Array<{
|
||||
service: ServiceInterface
|
||||
folders: CollectionObject[]
|
||||
folderTree: FolderNode[]
|
||||
service: ServiceObject
|
||||
loading: boolean
|
||||
loaded: boolean
|
||||
error: string | null
|
||||
}>
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const collectionsStore = useCollectionsStore()
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
select: [folder: CollectionObject]
|
||||
createFolder: [service: ServiceInterface, parentFolder: CollectionObject | null]
|
||||
editFolder: [service: ServiceInterface, folder: CollectionObject]
|
||||
createFolder: [service: ServiceObject, parentFolder: CollectionObject | null]
|
||||
editFolder: [service: ServiceObject, folder: CollectionObject]
|
||||
}>()
|
||||
|
||||
const getRootFolders = (service: ServiceObject): CollectionObject[] => {
|
||||
return collectionsStore.collectionsInCollection(service.provider, service.identifier, null)
|
||||
}
|
||||
|
||||
const getServiceFolders = (service: ServiceObject): CollectionObject[] => {
|
||||
return collectionsStore.collectionsForService(service.provider, service.identifier)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -61,19 +66,72 @@ const emit = defineEmits<{
|
||||
</template>
|
||||
|
||||
<FolderTreeNode
|
||||
v-for="node in group.folderTree"
|
||||
:key="`${node.folder.provider}-${node.folder.service}-${node.folder.identifier}`"
|
||||
:node="node"
|
||||
v-for="folder in getRootFolders(group.service)"
|
||||
:key="`${folder.provider}-${folder.service}-${folder.identifier}`"
|
||||
:folder="folder"
|
||||
:service="group.service"
|
||||
:selected-folder="selectedFolder"
|
||||
@select="emit('select', $event)"
|
||||
@create-subfolder="(service, parentFolder) => emit('createFolder', service, parentFolder)"
|
||||
@edit-folder="(service, folder) => emit('editFolder', service, folder)"
|
||||
/>
|
||||
|
||||
<v-list-item v-if="group.loading && getServiceFolders(group.service).length === 0" disabled class="folder-status-item">
|
||||
<template v-slot:prepend>
|
||||
<v-progress-circular indeterminate size="18" width="2" color="primary" />
|
||||
</template>
|
||||
<v-list-item-title>Loading folders</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item
|
||||
v-else-if="group.error && getServiceFolders(group.service).length === 0"
|
||||
disabled
|
||||
class="folder-status-item"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-alert-circle-outline" color="error" />
|
||||
</template>
|
||||
<v-list-item-title>Folders unavailable</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ group.error }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item
|
||||
v-else-if="group.loaded && getServiceFolders(group.service).length === 0"
|
||||
disabled
|
||||
class="folder-status-item"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-folder-off-outline" />
|
||||
</template>
|
||||
<v-list-item-title>No folders found</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list-group>
|
||||
|
||||
<!-- Single service - show folders directly -->
|
||||
<template v-else>
|
||||
<v-list-item
|
||||
class="account-header-item account-header-static"
|
||||
:title="group.service.label || 'Mail Account'"
|
||||
:subtitle="group.service.primaryAddress || undefined"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-email-outline" />
|
||||
</template>
|
||||
|
||||
<template v-slot:append>
|
||||
<v-btn
|
||||
icon="mdi-folder-plus"
|
||||
variant="text"
|
||||
size="small"
|
||||
density="compact"
|
||||
@click.stop="emit('createFolder', group.service, null)"
|
||||
>
|
||||
<v-icon>mdi-folder-plus</v-icon>
|
||||
<v-tooltip activator="parent" location="bottom">New Folder</v-tooltip>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-list-item>
|
||||
|
||||
<!-- Header with New Folder button -->
|
||||
<v-list-subheader class="d-flex align-center">
|
||||
<span class="flex-grow-1">FOLDERS</span>
|
||||
@@ -89,15 +147,45 @@ const emit = defineEmits<{
|
||||
</v-list-subheader>
|
||||
|
||||
<FolderTreeNode
|
||||
v-for="node in group.folderTree"
|
||||
:key="`${node.folder.provider}-${node.folder.service}-${node.folder.identifier}`"
|
||||
:node="node"
|
||||
v-for="folder in getRootFolders(group.service)"
|
||||
:key="`${folder.provider}-${folder.service}-${folder.identifier}`"
|
||||
:folder="folder"
|
||||
:service="group.service"
|
||||
:selected-folder="selectedFolder"
|
||||
@select="emit('select', $event)"
|
||||
@create-subfolder="(service, parentFolder) => emit('createFolder', service, parentFolder)"
|
||||
@edit-folder="(service, folder) => emit('editFolder', service, folder)"
|
||||
/>
|
||||
|
||||
<v-list-item v-if="group.loading && getServiceFolders(group.service).length === 0" disabled class="folder-status-item">
|
||||
<template v-slot:prepend>
|
||||
<v-progress-circular indeterminate size="18" width="2" color="primary" />
|
||||
</template>
|
||||
<v-list-item-title>Loading folders</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item
|
||||
v-else-if="group.error && getServiceFolders(group.service).length === 0"
|
||||
disabled
|
||||
class="folder-status-item"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-alert-circle-outline" color="error" />
|
||||
</template>
|
||||
<v-list-item-title>Folders unavailable</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ group.error }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item
|
||||
v-else-if="group.loaded && getServiceFolders(group.service).length === 0"
|
||||
disabled
|
||||
class="folder-status-item"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-folder-off-outline" />
|
||||
</template>
|
||||
<v-list-item-title>No folders found</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
@@ -114,8 +202,16 @@ const emit = defineEmits<{
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.account-header-static {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.account-header-item :deep(.v-list-item__prepend) {
|
||||
padding-inline-start: 4px;
|
||||
margin-inline-end: 2px;
|
||||
}
|
||||
|
||||
.folder-status-item {
|
||||
padding-inline-start: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,11 +3,11 @@ import { ref, computed, watch } from 'vue'
|
||||
import { useCollectionsStore } from '@MailManager/stores/collectionsStore'
|
||||
import { CollectionPropertiesObject } from '@MailManager/models/collection'
|
||||
import type { CollectionObject } from '@MailManager/models/collection'
|
||||
import type { ServiceInterface } from '@MailManager/types/service'
|
||||
import type { ServiceObject } from '@MailManager/models'
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
service: ServiceInterface
|
||||
service: ServiceObject
|
||||
folder: CollectionObject
|
||||
allFolders?: CollectionObject[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user