Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Introduced turbopack alias for canvas in next.config.mjs. - Updated package.json scripts for development and branding tasks. - Added new dependencies for Tiptap extensions. - Implemented new demo layouts for agenda, contacts, drive, and mail applications. - Enhanced globals.css for improved theming and splash screen animations. - Added OAuth callback handling for drive mounts. - Updated layout components to integrate new demo shells and improve structure.
246 lines
8.0 KiB
TypeScript
246 lines
8.0 KiB
TypeScript
"use client"
|
|
|
|
import { useMemo } from "react"
|
|
import { useLabels } from "@/lib/api/hooks/use-folder-label-queries"
|
|
import { useUnifiedFolders } from "@/lib/api/hooks/use-unified-folder-queries"
|
|
import { useImapFolders } from "@/lib/api/hooks/use-imap-folders"
|
|
import { useMailAccounts } from "@/lib/api/hooks/use-mail-queries"
|
|
import { useContactsList } from "@/lib/contacts/use-contacts-list"
|
|
import { fullContactDisplayName } from "@/lib/contacts/types"
|
|
import type { AgendaInvitationExclusion } from "@/lib/agenda/agenda-settings-types"
|
|
import { exclusionKey } from "@/lib/agenda/agenda-settings-types"
|
|
import {
|
|
buildDestinationMailboxes,
|
|
destinationEmailFromStoredKey,
|
|
} from "@/lib/agenda/agenda-destination-identities"
|
|
import { identityKey } from "@/lib/agenda/agenda-store"
|
|
import { useComposeIdentitiesStore } from "@/lib/stores/compose-identities-store"
|
|
|
|
export type AgendaImportSuggestion = AgendaInvitationExclusion & {
|
|
id: string
|
|
group: string
|
|
}
|
|
|
|
const MAILBOX_SUGGESTIONS: AgendaInvitationExclusion[] = [
|
|
{ type: "mailbox", value: "spam", label: "Spam" },
|
|
{ type: "mailbox", value: "trash", label: "Corbeille" },
|
|
{ type: "mailbox", value: "promotions", label: "Promotions" },
|
|
{ type: "mailbox", value: "social", label: "Réseaux sociaux" },
|
|
{ type: "mailbox", value: "updates", label: "Notifications" },
|
|
]
|
|
|
|
function matchesQuery(text: string, query: string): boolean {
|
|
return text.toLowerCase().includes(query.toLowerCase())
|
|
}
|
|
|
|
export function useAgendaDestinationMailboxes() {
|
|
const identities = useComposeIdentitiesStore((s) => s.identities)
|
|
const { data: accounts = [] } = useMailAccounts()
|
|
return useMemo(
|
|
() => buildDestinationMailboxes(identities, accounts.map((a) => a.email)),
|
|
[identities, accounts],
|
|
)
|
|
}
|
|
|
|
export function useAgendaInvitationSuggestions(
|
|
query: string,
|
|
taken: Set<string>,
|
|
opts?: { types?: AgendaInvitationExclusion["type"][] },
|
|
) {
|
|
const { contacts } = useContactsList()
|
|
const identities = useComposeIdentitiesStore((s) => s.identities)
|
|
const { data: accounts = [] } = useMailAccounts()
|
|
const destinationMailboxes = useMemo(
|
|
() => buildDestinationMailboxes(identities, accounts.map((a) => a.email)),
|
|
[identities, accounts],
|
|
)
|
|
const { data: labels = [] } = useLabels()
|
|
const { data: unifiedFolders = [] } = useUnifiedFolders("all")
|
|
const { folders: imapFolders = [] } = useImapFolders()
|
|
|
|
return useMemo(() => {
|
|
const q = query.trim()
|
|
const allowed = opts?.types
|
|
const includeType = (type: AgendaInvitationExclusion["type"]) =>
|
|
!allowed || allowed.includes(type)
|
|
|
|
const out: AgendaImportSuggestion[] = []
|
|
|
|
const seenSuggestionIds = new Set<string>()
|
|
const push = (suggestion: AgendaImportSuggestion) => {
|
|
if (seenSuggestionIds.has(suggestion.id)) return
|
|
seenSuggestionIds.add(suggestion.id)
|
|
out.push(suggestion)
|
|
}
|
|
|
|
if (includeType("not_in_contacts")) {
|
|
const allRule: AgendaInvitationExclusion = {
|
|
type: "not_in_contacts",
|
|
value: "",
|
|
label: "Toute personne pas dans les contacts",
|
|
}
|
|
const allId = exclusionKey(allRule)
|
|
if (!taken.has(allId) && (!q || matchesQuery(allRule.label, q))) {
|
|
push({ ...allRule, id: allId, group: "Règles contacts" })
|
|
}
|
|
const seenIdentityKeys = new Set<string>()
|
|
for (const identity of identities) {
|
|
const key = identityKey(identity.email, identity.accountId)
|
|
if (seenIdentityKeys.has(key)) continue
|
|
seenIdentityKeys.add(key)
|
|
const rule: AgendaInvitationExclusion = {
|
|
type: "not_in_contacts",
|
|
value: key,
|
|
label: `Personnes pas dans les contacts de ${identity.name || identity.email}`,
|
|
}
|
|
const id = exclusionKey(rule)
|
|
if (taken.has(id)) continue
|
|
if (q && !matchesQuery(rule.label, q) && !matchesQuery(identity.email, q)) continue
|
|
push({ ...rule, id, group: "Règles contacts" })
|
|
}
|
|
}
|
|
|
|
if (includeType("identity")) {
|
|
for (const mailbox of destinationMailboxes) {
|
|
const rule: AgendaInvitationExclusion = {
|
|
type: "identity",
|
|
value: mailbox.key,
|
|
label: mailbox.label,
|
|
}
|
|
const id = exclusionKey(rule)
|
|
if (taken.has(id)) continue
|
|
if (q && !matchesQuery(mailbox.label, q) && !matchesQuery(mailbox.key, q)) continue
|
|
push({ ...rule, id, group: "Mails de destination" })
|
|
}
|
|
}
|
|
|
|
if (includeType("contact")) {
|
|
for (const contact of contacts) {
|
|
const name = fullContactDisplayName(contact)
|
|
for (const { value: email } of contact.emails) {
|
|
if (!email) continue
|
|
const rule: AgendaInvitationExclusion = {
|
|
type: "contact",
|
|
value: email,
|
|
label: name ? `${name} <${email}>` : email,
|
|
}
|
|
const id = exclusionKey(rule)
|
|
if (taken.has(id)) continue
|
|
if (q && !matchesQuery(rule.label, q) && !matchesQuery(email, q)) continue
|
|
push({ ...rule, id, group: "Contacts" })
|
|
if (out.length > 40) break
|
|
}
|
|
}
|
|
}
|
|
|
|
if (includeType("email") && q.includes("@")) {
|
|
const rule: AgendaInvitationExclusion = {
|
|
type: "email",
|
|
value: q,
|
|
label: q,
|
|
}
|
|
const id = exclusionKey(rule)
|
|
if (!taken.has(id)) push({ ...rule, id, group: "Adresses" })
|
|
}
|
|
|
|
if (includeType("label")) {
|
|
for (const label of labels) {
|
|
const rule: AgendaInvitationExclusion = {
|
|
type: "label",
|
|
value: label.name,
|
|
label: `Libellé · ${label.name}`,
|
|
}
|
|
const id = exclusionKey(rule)
|
|
if (taken.has(id)) continue
|
|
if (q && !matchesQuery(label.name, q)) continue
|
|
push({ ...rule, id, group: "Libellés" })
|
|
}
|
|
}
|
|
|
|
if (includeType("folder")) {
|
|
const folders = [
|
|
...unifiedFolders.map((f) => ({
|
|
value: f.id,
|
|
label: `Dossier · ${f.name}`,
|
|
})),
|
|
...imapFolders.map((f) => ({
|
|
value: f.name,
|
|
label: `Dossier · ${f.name}`,
|
|
})),
|
|
]
|
|
for (const folder of folders) {
|
|
const rule: AgendaInvitationExclusion = {
|
|
type: "folder",
|
|
value: folder.value,
|
|
label: folder.label,
|
|
}
|
|
const id = exclusionKey(rule)
|
|
if (taken.has(id)) continue
|
|
if (q && !matchesQuery(folder.label, q)) continue
|
|
push({ ...rule, id, group: "Dossiers" })
|
|
}
|
|
}
|
|
|
|
if (includeType("mailbox")) {
|
|
for (const mailbox of MAILBOX_SUGGESTIONS) {
|
|
const id = exclusionKey(mailbox)
|
|
if (taken.has(id)) continue
|
|
if (q && !matchesQuery(mailbox.label, q)) continue
|
|
push({ ...mailbox, id, group: "Boîtes mail" })
|
|
}
|
|
}
|
|
|
|
return out.slice(0, 12)
|
|
}, [
|
|
contacts,
|
|
identities,
|
|
destinationMailboxes,
|
|
labels,
|
|
unifiedFolders,
|
|
imapFolders,
|
|
query,
|
|
taken,
|
|
opts?.types,
|
|
])
|
|
}
|
|
|
|
export function identityLabelFromKey(key: string): string {
|
|
const identities = useComposeIdentitiesStore.getState().identities
|
|
const found = identities.find((i) => identityKey(i.email, i.accountId) === key)
|
|
if (!found) return key
|
|
return `${found.name} <${found.email}>`
|
|
}
|
|
|
|
export function useAgendaSettingsIdentityOptions() {
|
|
const identities = useComposeIdentitiesStore((s) => s.identities)
|
|
return useMemo(() => {
|
|
const seen = new Set<string>()
|
|
const out: { key: string; label: string }[] = []
|
|
for (const identity of identities) {
|
|
const key = identityKey(identity.email, identity.accountId)
|
|
if (seen.has(key)) continue
|
|
seen.add(key)
|
|
out.push({
|
|
key,
|
|
label: `${identity.name} <${identity.email}>`,
|
|
})
|
|
}
|
|
return out
|
|
}, [identities])
|
|
}
|
|
|
|
export function useAgendaSettingsDestinationOptions() {
|
|
return useAgendaDestinationMailboxes()
|
|
}
|
|
|
|
/** Libellé d'une clé stockée (legacy identité ou email de destination). */
|
|
export function destinationLabelFromStoredKey(
|
|
key: string,
|
|
mailboxes: ReturnType<typeof buildDestinationMailboxes>,
|
|
): string {
|
|
return (
|
|
mailboxes.find((m) => m.key === destinationEmailFromStoredKey(key))?.label ??
|
|
destinationEmailFromStoredKey(key)
|
|
)
|
|
}
|