151 lines
4.3 KiB
TypeScript
151 lines
4.3 KiB
TypeScript
import type { Email } from "@/lib/email-data"
|
||
import {
|
||
folderTree as defaultFolderTree,
|
||
sidebarNavFolderIdToLabel as defaultSidebarNavFolderIdToLabel,
|
||
type FolderTreeNode,
|
||
} from "@/lib/sidebar-nav-data"
|
||
import { collectSubtreeFolderIds } from "@/lib/sidebar-nav-maps"
|
||
|
||
export type MailFolderFilterCtx = {
|
||
starredEmailIds: string[]
|
||
importantEmailIds: string[]
|
||
}
|
||
|
||
/** Carte navigation dynamique (sidebar). Si absent, valeurs par défaut du module. */
|
||
export type MailNavFolderMaps = {
|
||
folderIdToLabel: Record<string, string>
|
||
folderTree: FolderTreeNode[]
|
||
}
|
||
|
||
const CATEGORY_EMAIL_TAB_IDS = new Set([
|
||
"social",
|
||
"promotions",
|
||
"updates",
|
||
"forums",
|
||
])
|
||
|
||
/** Catégories latérales (sidebar) → libellé optionnel sur `email.labels`. */
|
||
const SIDEBAR_CATEGORY_EXTRA_LABEL: Partial<Record<string, string>> = {
|
||
purchases: "Achats",
|
||
travel: "Déplacements",
|
||
finance: "Finance",
|
||
}
|
||
|
||
function effectiveStarred(email: Email, ctx: MailFolderFilterCtx): boolean {
|
||
return ctx.starredEmailIds.includes(email.id) || email.starred
|
||
}
|
||
|
||
function effectiveImportant(email: Email, ctx: MailFolderFilterCtx): boolean {
|
||
return ctx.importantEmailIds.includes(email.id) || email.important
|
||
}
|
||
|
||
function isInInbox(email: Email): boolean {
|
||
if (email.spam) return false
|
||
const ls = email.labels
|
||
if (!ls?.length) return true
|
||
return ls.includes("inbox")
|
||
}
|
||
|
||
function resolveNavMaps(maps?: MailNavFolderMaps | null): MailNavFolderMaps {
|
||
if (maps) return maps
|
||
return {
|
||
folderIdToLabel: defaultSidebarNavFolderIdToLabel as Record<string, string>,
|
||
folderTree: defaultFolderTree,
|
||
}
|
||
}
|
||
|
||
function emailHasAnyLabel(email: Email, labels: string[]): boolean {
|
||
const ls = email.labels
|
||
if (!ls?.length) return false
|
||
const lower = new Set(ls.map((x) => x.toLowerCase()))
|
||
return labels.some((lab) => lower.has(lab.toLowerCase()))
|
||
}
|
||
|
||
function matchesFolderLabelRow(
|
||
email: Email,
|
||
folderId: string,
|
||
maps: MailNavFolderMaps
|
||
): boolean {
|
||
const label = maps.folderIdToLabel[folderId]
|
||
if (!label) return false
|
||
return emailHasAnyLabel(email, [label])
|
||
}
|
||
|
||
/** Dossier hiérarchique : ce nœud ou n'importe quel sous-dossier (décompte unique par mail). */
|
||
function matchesLabelNav(
|
||
email: Email,
|
||
folderId: string,
|
||
maps: MailNavFolderMaps,
|
||
subtreeIdsCache?: Map<string, string[] | null>
|
||
): boolean {
|
||
let subtreeIds: string[] | null
|
||
if (subtreeIdsCache) {
|
||
if (!subtreeIdsCache.has(folderId)) {
|
||
subtreeIdsCache.set(
|
||
folderId,
|
||
collectSubtreeFolderIds(maps.folderTree, folderId)
|
||
)
|
||
}
|
||
subtreeIds = subtreeIdsCache.get(folderId) ?? null
|
||
} else {
|
||
subtreeIds = collectSubtreeFolderIds(maps.folderTree, folderId)
|
||
}
|
||
if (subtreeIds) {
|
||
return subtreeIds.some((id) => matchesFolderLabelRow(email, id, maps))
|
||
}
|
||
return matchesFolderLabelRow(email, folderId, maps)
|
||
}
|
||
|
||
export function emailMatchesFolder(
|
||
email: Email,
|
||
folderId: string,
|
||
ctx: MailFolderFilterCtx,
|
||
maps?: MailNavFolderMaps | null,
|
||
/** Réutiliser entre appels (ex. `computeFolderUnreadCounts`) pour éviter de rescanner l’arbre à chaque mail. */
|
||
subtreeIdsCache?: Map<string, string[] | null>
|
||
): boolean {
|
||
const nav = resolveNavMaps(maps)
|
||
|
||
switch (folderId) {
|
||
case "inbox":
|
||
return isInInbox(email)
|
||
case "starred":
|
||
return effectiveStarred(email, ctx)
|
||
case "snoozed":
|
||
return email.labels?.includes("snoozed") ?? false
|
||
case "important":
|
||
return effectiveImportant(email, ctx)
|
||
case "sent":
|
||
return email.labels?.includes("sent") ?? false
|
||
case "drafts":
|
||
return email.labels?.includes("drafts") ?? false
|
||
case "scheduled":
|
||
return email.labels?.includes("scheduled") ?? false
|
||
case "spam":
|
||
return email.spam === true || (email.labels?.includes("spam") ?? false)
|
||
case "notifications":
|
||
return email.category === "updates"
|
||
case "purchases":
|
||
case "travel":
|
||
case "finance": {
|
||
const extra = SIDEBAR_CATEGORY_EXTRA_LABEL[folderId]
|
||
if (!extra) return false
|
||
return email.labels?.includes(extra) ?? false
|
||
}
|
||
default:
|
||
break
|
||
}
|
||
|
||
if (CATEGORY_EMAIL_TAB_IDS.has(folderId)) {
|
||
return email.category === folderId
|
||
}
|
||
|
||
if (nav.folderIdToLabel[folderId]) {
|
||
return matchesLabelNav(email, folderId, nav, subtreeIdsCache)
|
||
}
|
||
|
||
if (email.labels?.includes(folderId)) return true
|
||
|
||
return false
|
||
}
|