ultisuite-client/lib/mail-settings/imap-folder-tree.ts
2026-05-25 13:52:40 +02:00

99 lines
3.0 KiB
TypeScript

import type { ApiFolder } from '@/lib/api/types'
import type { FolderTreeNode } from '@/lib/sidebar-nav-maps'
function imapPathDelimiter(remoteNames: string[]): string {
if (remoteNames.some((n) => n.includes('/'))) return '/'
if (remoteNames.some((n) => n.includes('.'))) return '.'
return '/'
}
function splitImapPath(remoteName: string, delimiter: string): string[] {
return remoteName.split(delimiter).filter(Boolean)
}
/** Custom IMAP folders (system folders use fixed route slugs in the sidebar). */
export function filterImapFoldersForNav(
folders: ApiFolder[],
accountId?: string
): ApiFolder[] {
return folders.filter(
(folder) =>
folder.folder_type === 'custom' &&
(!accountId || folder.account_id === accountId)
)
}
/** Build a hierarchical tree from IMAP folder paths (remote_name). */
export function buildFolderTreeFromImap(
folders: ApiFolder[],
accountId?: string
): FolderTreeNode[] {
const custom = filterImapFoldersForNav(folders, accountId).sort((a, b) =>
a.remote_name.localeCompare(b.remote_name)
)
if (custom.length === 0) return []
const delimiter = imapPathDelimiter(custom.map((f) => f.remote_name))
const nodesByRemote = new Map(custom.map((f) => [f.remote_name, f]))
const childrenOf = new Map<string | null, ApiFolder[]>()
for (const folder of custom) {
const segments = splitImapPath(folder.remote_name, delimiter)
let parentRemote: string | null = null
for (let i = 0; i < segments.length - 1; i++) {
const prefix = segments.slice(0, i + 1).join(delimiter)
if (nodesByRemote.has(prefix)) {
parentRemote = prefix
}
}
const list = childrenOf.get(parentRemote) ?? []
list.push(folder)
childrenOf.set(parentRemote, list)
}
function walk(parentRemote: string | null): FolderTreeNode[] {
const kids = childrenOf.get(parentRemote) ?? []
return kids
.sort((a, b) => a.name.localeCompare(b.name))
.map((folder) => {
const node: FolderTreeNode = {
id: folder.id,
label: folder.name,
}
if (folder.unread_count > 0) node.count = folder.unread_count
const children = walk(folder.remote_name)
if (children.length) node.children = children
return node
})
}
return walk(null)
}
export type ImapFolderSettingsNode = {
id: string
label: string
remoteName?: string
children?: ImapFolderSettingsNode[]
}
/** Settings tree with IMAP remote paths for display. */
export function buildImapFolderSettingsTree(
folders: ApiFolder[],
accountId: string
): ImapFolderSettingsNode[] {
const custom = filterImapFoldersForNav(folders, accountId)
const remoteById = new Map(custom.map((f) => [f.id, f.remote_name]))
function attachRemote(nodes: FolderTreeNode[]): ImapFolderSettingsNode[] {
return nodes.map((node) => ({
id: node.id,
label: node.label,
remoteName: remoteById.get(node.id),
children: node.children?.length ? attachRemote(node.children) : undefined,
}))
}
return attachRemote(buildFolderTreeFromImap(folders, accountId))
}