188 lines
5.9 KiB
TypeScript
188 lines
5.9 KiB
TypeScript
"use client"
|
|
|
|
import { create } from "zustand"
|
|
import { persist } from "zustand/middleware"
|
|
|
|
/**
|
|
* Persistent mail store — survives across navigations and page reloads.
|
|
* Tracks user-driven mutations on top of the static `emails` array from email-data.ts.
|
|
* Designed for future server sync: every action is a discrete delta.
|
|
*/
|
|
|
|
export type LabelEditState = {
|
|
additions: Record<string, string[]>
|
|
removals: Record<string, string[]>
|
|
}
|
|
|
|
type MailStoreState = {
|
|
readOverrides: Record<string, boolean>
|
|
starredIds: string[]
|
|
importantIds: string[]
|
|
labelEdits: LabelEditState
|
|
hiddenEmailIds: string[]
|
|
seenEmailIds: string[]
|
|
recentMoveTargets: string[]
|
|
/** Dernières boîtes visitées (clés `mailNavVisitKey`), la plus récente en tête. */
|
|
recentFolderVisits: string[]
|
|
}
|
|
|
|
type MailStoreActions = {
|
|
setReadOverride: (id: string, read: boolean) => void
|
|
setReadOverrides: (overrides: Record<string, boolean>) => void
|
|
toggleStar: (id: string) => void
|
|
setStar: (id: string, starred: boolean) => void
|
|
toggleImportant: (id: string) => void
|
|
setImportant: (id: string, important: boolean) => void
|
|
addLabel: (emailId: string, label: string) => void
|
|
removeLabel: (emailId: string, label: string) => void
|
|
setLabelEdits: (updater: (prev: LabelEditState) => LabelEditState) => void
|
|
hideEmail: (id: string) => void
|
|
hideEmails: (ids: string[]) => void
|
|
unhideEmail: (id: string) => void
|
|
markSeen: (id: string) => void
|
|
resetHidden: () => void
|
|
pushRecentMoveTarget: (targetId: string) => void
|
|
pushRecentFolderVisit: (visitKey: string) => void
|
|
}
|
|
|
|
export const useMailStore = create<MailStoreState & MailStoreActions>()(
|
|
persist(
|
|
(set) => ({
|
|
readOverrides: {},
|
|
starredIds: [],
|
|
importantIds: [],
|
|
labelEdits: { additions: {}, removals: {} },
|
|
hiddenEmailIds: [],
|
|
seenEmailIds: [],
|
|
recentMoveTargets: [],
|
|
recentFolderVisits: [],
|
|
|
|
setReadOverride: (id, read) =>
|
|
set((s) => ({ readOverrides: { ...s.readOverrides, [id]: read } })),
|
|
|
|
setReadOverrides: (overrides) =>
|
|
set((s) => ({ readOverrides: { ...s.readOverrides, ...overrides } })),
|
|
|
|
toggleStar: (id) =>
|
|
set((s) => ({
|
|
starredIds: s.starredIds.includes(id)
|
|
? s.starredIds.filter((x) => x !== id)
|
|
: [...s.starredIds, id],
|
|
})),
|
|
|
|
setStar: (id, starred) =>
|
|
set((s) => ({
|
|
starredIds: starred
|
|
? s.starredIds.includes(id) ? s.starredIds : [...s.starredIds, id]
|
|
: s.starredIds.filter((x) => x !== id),
|
|
})),
|
|
|
|
toggleImportant: (id) =>
|
|
set((s) => ({
|
|
importantIds: s.importantIds.includes(id)
|
|
? s.importantIds.filter((x) => x !== id)
|
|
: [...s.importantIds, id],
|
|
})),
|
|
|
|
setImportant: (id, important) =>
|
|
set((s) => ({
|
|
importantIds: important
|
|
? s.importantIds.includes(id) ? s.importantIds : [...s.importantIds, id]
|
|
: s.importantIds.filter((x) => x !== id),
|
|
})),
|
|
|
|
addLabel: (emailId, label) =>
|
|
set((s) => {
|
|
const curr = s.labelEdits.additions[emailId] ?? []
|
|
if (curr.some((l) => l.toLowerCase() === label.toLowerCase())) return s
|
|
return {
|
|
labelEdits: {
|
|
additions: { ...s.labelEdits.additions, [emailId]: [...curr, label] },
|
|
removals: {
|
|
...s.labelEdits.removals,
|
|
[emailId]: (s.labelEdits.removals[emailId] ?? []).filter(
|
|
(r) => r.toLowerCase() !== label.toLowerCase()
|
|
),
|
|
},
|
|
},
|
|
}
|
|
}),
|
|
|
|
removeLabel: (emailId, label) =>
|
|
set((s) => {
|
|
const curr = s.labelEdits.removals[emailId] ?? []
|
|
if (curr.some((l) => l.toLowerCase() === label.toLowerCase())) return s
|
|
return {
|
|
labelEdits: {
|
|
removals: { ...s.labelEdits.removals, [emailId]: [...curr, label] },
|
|
additions: {
|
|
...s.labelEdits.additions,
|
|
[emailId]: (s.labelEdits.additions[emailId] ?? []).filter(
|
|
(a) => a.toLowerCase() !== label.toLowerCase()
|
|
),
|
|
},
|
|
},
|
|
}
|
|
}),
|
|
|
|
setLabelEdits: (updater) =>
|
|
set((s) => ({ labelEdits: updater(s.labelEdits) })),
|
|
|
|
hideEmail: (id) =>
|
|
set((s) => ({
|
|
hiddenEmailIds: s.hiddenEmailIds.includes(id)
|
|
? s.hiddenEmailIds
|
|
: [...s.hiddenEmailIds, id],
|
|
})),
|
|
|
|
hideEmails: (ids) =>
|
|
set((s) => {
|
|
const existing = new Set(s.hiddenEmailIds)
|
|
const toAdd = ids.filter((id) => !existing.has(id))
|
|
return toAdd.length > 0
|
|
? { hiddenEmailIds: [...s.hiddenEmailIds, ...toAdd] }
|
|
: s
|
|
}),
|
|
|
|
unhideEmail: (id) =>
|
|
set((s) => ({
|
|
hiddenEmailIds: s.hiddenEmailIds.filter((x) => x !== id),
|
|
})),
|
|
|
|
markSeen: (id) =>
|
|
set((s) => ({
|
|
seenEmailIds: s.seenEmailIds.includes(id)
|
|
? s.seenEmailIds
|
|
: [...s.seenEmailIds, id],
|
|
})),
|
|
|
|
resetHidden: () => set({ hiddenEmailIds: [] }),
|
|
|
|
pushRecentMoveTarget: (targetId) =>
|
|
set((s) => {
|
|
const MAX = 5
|
|
const filtered = s.recentMoveTargets.filter((t) => t !== targetId)
|
|
return { recentMoveTargets: [targetId, ...filtered].slice(0, MAX) }
|
|
}),
|
|
|
|
pushRecentFolderVisit: (visitKey) =>
|
|
set((s) => {
|
|
const MAX = 4
|
|
const filtered = s.recentFolderVisits.filter((k) => k !== visitKey)
|
|
return { recentFolderVisits: [visitKey, ...filtered].slice(0, MAX) }
|
|
}),
|
|
}),
|
|
{
|
|
name: "ultimail-mail-state",
|
|
version: 2,
|
|
migrate: (persisted, version) => {
|
|
const state = persisted as MailStoreState
|
|
if (version < 2) {
|
|
return { ...state, recentFolderVisits: [] }
|
|
}
|
|
return state
|
|
},
|
|
}
|
|
)
|
|
)
|