feat(mail): 优化外部邮箱侧边栏交互
Docker Release / Check web and api (push) Waiting to run
Docker Release / Resolve release tag (push) Blocked by required conditions
Docker Release / Build and publish all-in-one (push) Blocked by required conditions
Docker Release / Build and publish api (push) Blocked by required conditions
Docker Release / Build and publish web (push) Blocked by required conditions
Docker Release / Build and publish dovecot (push) Blocked by required conditions
Docker Release / Build and publish postfix (push) Blocked by required conditions
Docker Release / Build and publish rspamd (push) Blocked by required conditions
Docker Release / Create GitHub release (push) Blocked by required conditions
Docker Release / Check web and api (push) Waiting to run
Docker Release / Resolve release tag (push) Blocked by required conditions
Docker Release / Build and publish all-in-one (push) Blocked by required conditions
Docker Release / Build and publish api (push) Blocked by required conditions
Docker Release / Build and publish web (push) Blocked by required conditions
Docker Release / Build and publish dovecot (push) Blocked by required conditions
Docker Release / Build and publish postfix (push) Blocked by required conditions
Docker Release / Build and publish rspamd (push) Blocked by required conditions
Docker Release / Create GitHub release (push) Blocked by required conditions
- 支持外部邮箱账号展开/收起,并在切换时自动保持当前账号展开。 - 增加外部邮箱显示主标题与副标题,提升账号信息可读性。 - 在外部 IMAP 关闭或账号列表变化时,清理已展开状态。
This commit is contained in:
@@ -99,6 +99,7 @@ export function MailPage() {
|
|||||||
const [mailFilter, setMailFilter] = React.useState<MailFilter>("all")
|
const [mailFilter, setMailFilter] = React.useState<MailFilter>("all")
|
||||||
const [selectedMailboxId, setSelectedMailboxId] = React.useState(() => localStorage.getItem("lanqin:selected-mailbox") || "")
|
const [selectedMailboxId, setSelectedMailboxId] = React.useState(() => localStorage.getItem("lanqin:selected-mailbox") || "")
|
||||||
const [selectedExternalAccountId, setSelectedExternalAccountId] = React.useState("")
|
const [selectedExternalAccountId, setSelectedExternalAccountId] = React.useState("")
|
||||||
|
const [expandedExternalAccountIds, setExpandedExternalAccountIds] = React.useState<string[]>([])
|
||||||
const [externalFolder, setExternalFolder] = React.useState("INBOX")
|
const [externalFolder, setExternalFolder] = React.useState("INBOX")
|
||||||
const [darkMode, setDarkMode] = React.useState(getInitialTheme)
|
const [darkMode, setDarkMode] = React.useState(getInitialTheme)
|
||||||
const [displayMode] = useDisplayMode()
|
const [displayMode] = useDisplayMode()
|
||||||
@@ -164,8 +165,16 @@ export function MailPage() {
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (externalImapEnabled) return
|
if (externalImapEnabled) return
|
||||||
setSelectedExternalAccountId("")
|
setSelectedExternalAccountId("")
|
||||||
|
setExpandedExternalAccountIds([])
|
||||||
if (mailView === "external") setMailView("folder")
|
if (mailView === "external") setMailView("folder")
|
||||||
}, [externalImapEnabled, mailView])
|
}, [externalImapEnabled, mailView])
|
||||||
|
React.useEffect(() => {
|
||||||
|
const ids = new Set((externalMailAccounts.data?.items || []).map((item) => item.id))
|
||||||
|
setExpandedExternalAccountIds((current) => {
|
||||||
|
const next = current.filter((id) => ids.has(id))
|
||||||
|
return next.length === current.length ? current : next
|
||||||
|
})
|
||||||
|
}, [externalMailAccounts.data?.items])
|
||||||
const inboxProbe = useQuery({
|
const inboxProbe = useQuery({
|
||||||
queryKey: ["mail-notifications", activeMailboxId],
|
queryKey: ["mail-notifications", activeMailboxId],
|
||||||
queryFn: () => api.messages("Inbox", "", "", activeMailboxId),
|
queryFn: () => api.messages("Inbox", "", "", activeMailboxId),
|
||||||
@@ -761,6 +770,7 @@ export function MailPage() {
|
|||||||
}
|
}
|
||||||
function openExternalFolder(account: ExternalImapAccount, folderName = "INBOX") {
|
function openExternalFolder(account: ExternalImapAccount, folderName = "INBOX") {
|
||||||
setSelectedExternalAccountId(account.id)
|
setSelectedExternalAccountId(account.id)
|
||||||
|
setExpandedExternalAccountIds([account.id])
|
||||||
setExternalFolder(folderName)
|
setExternalFolder(folderName)
|
||||||
setMailView("external")
|
setMailView("external")
|
||||||
setSelectedLabelId("")
|
setSelectedLabelId("")
|
||||||
@@ -768,6 +778,21 @@ export function MailPage() {
|
|||||||
setMailFilter("all")
|
setMailFilter("all")
|
||||||
setMobileSidebarOpen(false)
|
setMobileSidebarOpen(false)
|
||||||
}
|
}
|
||||||
|
function toggleExternalAccount(account: ExternalImapAccount) {
|
||||||
|
if (sidebarCollapsed) {
|
||||||
|
openExternalFolder(account, "INBOX")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const expanded = expandedExternalAccountIds.includes(account.id)
|
||||||
|
if (expanded) {
|
||||||
|
setExpandedExternalAccountIds((items) => items.filter((id) => id !== account.id))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setExpandedExternalAccountIds([account.id])
|
||||||
|
if (selectedExternalAccountId !== account.id || mailView !== "external") {
|
||||||
|
openExternalFolder(account, "INBOX")
|
||||||
|
}
|
||||||
|
}
|
||||||
function reorderCustomFolder(draggedId: string, target: FolderDropTarget) {
|
function reorderCustomFolder(draggedId: string, target: FolderDropTarget) {
|
||||||
if (!canOrganizeMail || reorderFolders.isPending) return
|
if (!canOrganizeMail || reorderFolders.isPending) return
|
||||||
const foldersByID = new Map((folders.data?.items || []).map((item) => [item.id, item]))
|
const foldersByID = new Map((folders.data?.items || []).map((item) => [item.id, item]))
|
||||||
@@ -1041,20 +1066,31 @@ export function MailPage() {
|
|||||||
{!sidebarCollapsed && <SidebarGroupLabel>外部邮箱</SidebarGroupLabel>}
|
{!sidebarCollapsed && <SidebarGroupLabel>外部邮箱</SidebarGroupLabel>}
|
||||||
<SidebarGroupContent>
|
<SidebarGroupContent>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
{externalAccountItems.map((account) => (
|
{externalAccountItems.map((account) => {
|
||||||
|
const expanded = expandedExternalAccountIds.includes(account.id)
|
||||||
|
return (
|
||||||
<React.Fragment key={account.id}>
|
<React.Fragment key={account.id}>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton
|
<SidebarMenuButton
|
||||||
isActive={mailView === "external" && selectedExternalAccountId === account.id}
|
isActive={mailView === "external" && selectedExternalAccountId === account.id}
|
||||||
|
size={sidebarCollapsed ? "default" : "lg"}
|
||||||
className={cn(sidebarCollapsed && "justify-center px-0")}
|
className={cn(sidebarCollapsed && "justify-center px-0")}
|
||||||
onClick={() => openExternalFolder(account, "INBOX")}
|
title={`${externalAccountLabel(account)}${account.name && account.name !== externalAccountLabel(account) ? ` · ${account.name}` : ""}`}
|
||||||
|
onClick={() => toggleExternalAccount(account)}
|
||||||
>
|
>
|
||||||
<Mail className="h-4 w-4" />
|
<Mail className="h-4 w-4" />
|
||||||
{!sidebarCollapsed && <span>{account.name}</span>}
|
{!sidebarCollapsed && (
|
||||||
{!sidebarCollapsed && <Badge variant="outline" className="ml-auto">{account.storageMode === "local" ? "同步" : "直连"}</Badge>}
|
<>
|
||||||
|
<span className="min-w-0 flex-1">
|
||||||
|
<span className="block truncate">{externalAccountLabel(account)}</span>
|
||||||
|
<span className="block truncate text-xs font-normal text-muted-foreground">{externalAccountSubtitle(account)}</span>
|
||||||
|
</span>
|
||||||
|
<ChevronDown className={cn("h-4 w-4 text-muted-foreground transition-transform", expanded && "rotate-180")} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
{!sidebarCollapsed && mailView === "external" && selectedExternalAccountId === account.id && externalFolderItems.map((item) => (
|
{!sidebarCollapsed && expanded && externalFolderItems.map((item) => (
|
||||||
<SidebarMenuItem key={`${account.id}-${item.name}`}>
|
<SidebarMenuItem key={`${account.id}-${item.name}`}>
|
||||||
<SidebarMenuButton
|
<SidebarMenuButton
|
||||||
isActive={externalFolder === item.name}
|
isActive={externalFolder === item.name}
|
||||||
@@ -1067,8 +1103,9 @@ export function MailPage() {
|
|||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
))}
|
))}
|
||||||
|
{!sidebarCollapsed && expanded && externalFolders.isLoading && <FolderSkeleton />}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
)})}
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarGroupContent>
|
</SidebarGroupContent>
|
||||||
</SidebarGroup>}
|
</SidebarGroup>}
|
||||||
@@ -1558,6 +1595,17 @@ function getEmptyMessage(mailView: MailView, folder: string, total: number) {
|
|||||||
return "当前文件夹没有邮件"
|
return "当前文件夹没有邮件"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function externalAccountLabel(account: ExternalImapAccount) {
|
||||||
|
return account.oauthEmail || account.username || account.name || "外部邮箱"
|
||||||
|
}
|
||||||
|
|
||||||
|
function externalAccountSubtitle(account: ExternalImapAccount) {
|
||||||
|
const label = externalAccountLabel(account)
|
||||||
|
const mode = account.storageMode === "local" ? "同步" : "直连"
|
||||||
|
const name = account.name && account.name !== label ? account.name : ""
|
||||||
|
return [name, account.host, mode].filter(Boolean).join(" · ")
|
||||||
|
}
|
||||||
|
|
||||||
function NoMailboxState({ onOpenSettings }: { onOpenSettings: () => void }) {
|
function NoMailboxState({ onOpenSettings }: { onOpenSettings: () => void }) {
|
||||||
return (
|
return (
|
||||||
<div className="grid min-h-0 flex-1 place-items-center p-6">
|
<div className="grid min-h-0 flex-1 place-items-center p-6">
|
||||||
|
|||||||
Reference in New Issue
Block a user