99 lines
3.0 KiB
TypeScript
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))
|
|
}
|