Compare commits

..

5 Commits

Author SHA1 Message Date
zxyszx 7fc91f2ed9 release: prepare v1.2.41
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
2026-08-13 02:09:04 +08:00
zxyszx c482e5b896 release: prepare v1.2.40
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
2026-08-13 01:39:33 +08:00
zxyszx 07e62e9c2d test: wait for manual backup completion 2026-08-13 01:38:59 +08:00
zxyszx 5797b472d9 release: prepare v1.2.39
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
2026-08-13 01:34:19 +08:00
zxyszx 3caee11e38 release: prepare v1.2.38
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
2026-08-13 01:24:12 +08:00
10 changed files with 63 additions and 10 deletions
+6
View File
@@ -0,0 +1,6 @@
- 写信发送拆分按钮统一使用 Gmail 风格蓝色主操作,浅色与暗色模式都保持清晰白字及一致悬停状态。
- 修复“定时发送”菜单按左侧展开导致右边框越过写信窗口的问题,菜单改为右对齐并增加边缘避让和垂直间距。
- 将默认主操作按钮从通用强调色中拆分,暗色主题下后台、个人设置、规则、弹窗确认、登录注册等页面统一使用深灰底白字;勾选框、进度条、选中态等仍保留清晰的强调色。
- 已保存的浅色或暗色主题现在会在应用启动时统一恢复,切换到后台、登录及其他页面后不再丢失主题状态。
**完整更新日志**[v1.2.37...v1.2.38](https://github.com/zxyszx/NewSzxcn-Email/compare/v1.2.37...v1.2.38)
+4
View File
@@ -0,0 +1,4 @@
- 修复桌面端文件夹或标签较多时侧栏被内容撑出视口、底部项目无法访问的问题;账号、邮箱切换和写信入口保持固定,邮件导航区域独立滚动。
- 同步包含 v1.2.38 的暗色主按钮与 Gmail 蓝色发送按钮修复,更新完成后写信发送按钮在浅色和暗色模式下均为蓝底白字。
**完整更新日志**[v1.2.38...v1.2.39](https://github.com/zxyszx/NewSzxcn-Email/compare/v1.2.38...v1.2.39)
+6
View File
@@ -0,0 +1,6 @@
- 修复桌面端文件夹或标签较多时侧栏被内容撑出视口、底部项目无法访问的问题;账号、邮箱切换和写信入口保持固定,邮件导航区域独立滚动。
- 写信发送按钮在浅色和暗色模式下统一为 Gmail 风格蓝底白字,定时发送菜单在桌面和手机端均不会越过写信窗口。
- 暗色主题默认主操作按钮改为深灰底白字,并与勾选框、进度条、危险按钮等语义颜色分离;进入后台或登录页后也会保持已选择的主题。
- 修复备份后台任务测试结束过早导致发布流程偶发失败的问题。
**完整更新日志**[v1.2.39...v1.2.40](https://github.com/zxyszx/NewSzxcn-Email/compare/v1.2.39...v1.2.40)
+3
View File
@@ -0,0 +1,3 @@
- 调整写信页定时发送菜单宽度,使其与“发送 + 下拉箭头”组合按钮左右边缘完全对齐,同时保留桌面和手机端边界避让。
**完整更新日志**[v1.2.40...v1.2.41](https://github.com/zxyszx/NewSzxcn-Email/compare/v1.2.40...v1.2.41)
+1 -1
View File
@@ -1 +1 @@
1.2.37 1.2.41
@@ -316,6 +316,19 @@ func TestManualBackupReusesSavedPassword(t *testing.T) {
if err != nil || password != "SharedBackupPassword9" { if err != nil || password != "SharedBackupPassword9" {
t.Fatalf("saved password changed: %q, %v", password, err) t.Fatalf("saved password changed: %q, %v", password, err)
} }
deadline := time.Now().Add(10 * time.Second)
for {
a.backupMu.Lock()
status := a.backupJob.Status
a.backupMu.Unlock()
if status != "running" {
break
}
if time.Now().After(deadline) {
t.Fatal("manual backup did not finish before timeout")
}
time.Sleep(20 * time.Millisecond)
}
} }
func TestPublicServerIPValidation(t *testing.T) { func TestPublicServerIPValidation(t *testing.T) {
+1 -1
View File
@@ -10,7 +10,7 @@ const buttonVariants = cva(
variants: { variants: {
variant: { variant: {
default: default:
"bg-primary text-primary-foreground hover:bg-primary/90", "bg-[hsl(var(--action-primary))] text-[hsl(var(--action-primary-foreground))] hover:bg-[hsl(var(--action-primary)/0.9)]",
destructive: destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90", "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: outline:
+18
View File
@@ -12,6 +12,8 @@
--popover-foreground: 0 0% 9%; --popover-foreground: 0 0% 9%;
--primary: 0 0% 12%; --primary: 0 0% 12%;
--primary-foreground: 0 0% 98%; --primary-foreground: 0 0% 98%;
--action-primary: 0 0% 12%;
--action-primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%; --secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 12%; --secondary-foreground: 0 0% 12%;
--muted: 0 0% 96.1%; --muted: 0 0% 96.1%;
@@ -33,6 +35,8 @@
--sidebar-accent-foreground: 0 0% 12%; --sidebar-accent-foreground: 0 0% 12%;
--sidebar-border: 0 0% 89.8%; --sidebar-border: 0 0% 89.8%;
--sidebar-ring: 0 0% 42%; --sidebar-ring: 0 0% 42%;
--compose-send: 217 89% 43%;
--compose-send-hover: 216 94% 32%;
} }
* { @apply border-border; } * { @apply border-border; }
@@ -61,6 +65,8 @@
--popover-foreground: 0 0% 98%; --popover-foreground: 0 0% 98%;
--primary: 0 0% 98%; --primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%; --primary-foreground: 240 5.9% 10%;
--action-primary: 240 4% 24%;
--action-primary-foreground: 0 0% 98%;
--secondary: 240 3.7% 15.9%; --secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%; --secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%; --muted: 240 3.7% 15.9%;
@@ -80,10 +86,22 @@
--sidebar-accent-foreground: 240 4.8% 95.9%; --sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%; --sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%; --sidebar-ring: 217.2 91.2% 59.8%;
--compose-send: 217 89% 52%;
--compose-send-hover: 214 94% 60%;
} }
} }
@layer components { @layer components {
.compose-send-button.compose-send-button {
background: hsl(var(--compose-send));
color: white;
}
.compose-send-button.compose-send-button:hover {
background: hsl(var(--compose-send-hover));
color: white;
}
.mail-shell-grid { .mail-shell-grid {
display: grid; display: grid;
grid-template-columns: var(--app-sidebar-width, 18rem) minmax(0, 1fr); grid-template-columns: var(--app-sidebar-width, 18rem) minmax(0, 1fr);
+3
View File
@@ -4,6 +4,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { Navigate, RouterProvider, createBrowserRouter } from "react-router-dom" import { Navigate, RouterProvider, createBrowserRouter } from "react-router-dom"
import { Toaster } from "@/components/ui/toaster" import { Toaster } from "@/components/ui/toaster"
import { LanguageDomSync } from "@/lib/language" import { LanguageDomSync } from "@/lib/language"
import { applyTheme, getInitialTheme } from "@/lib/theme"
import { ProtectedLayout } from "@/components/protected-layout" import { ProtectedLayout } from "@/components/protected-layout"
import { AdminOnly } from "@/components/admin-only" import { AdminOnly } from "@/components/admin-only"
import "./index.css" import "./index.css"
@@ -15,6 +16,8 @@ const AdminPage = React.lazy(() => import("@/pages/admin").then((module) => ({ d
const ProfilePage = React.lazy(() => import("@/pages/profile").then((module) => ({ default: module.ProfilePage }))) const ProfilePage = React.lazy(() => import("@/pages/profile").then((module) => ({ default: module.ProfilePage })))
const NotFoundPage = React.lazy(() => import("@/pages/not-found").then((module) => ({ default: module.NotFoundPage }))) const NotFoundPage = React.lazy(() => import("@/pages/not-found").then((module) => ({ default: module.NotFoundPage })))
applyTheme(getInitialTheme())
const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: false, staleTime: 10_000 } } }) const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: false, staleTime: 10_000 } } })
const router = createBrowserRouter([ const router = createBrowserRouter([
{ path: "/login", element: <LoginPage /> }, { path: "/login", element: <LoginPage /> },
+8 -8
View File
@@ -1307,8 +1307,8 @@ export function MailPage() {
setAdvancedSearchOpen(false) setAdvancedSearchOpen(false)
} }
const sidebarContent = ( const sidebarContent = (
<Sidebar collapsible="none" className="h-full w-full border-r border-border bg-sidebar text-sidebar-foreground"> <Sidebar collapsible="none" className="h-full min-h-0 w-full overflow-hidden border-r border-border bg-sidebar text-sidebar-foreground">
<SidebarHeader className={cn("pb-2 pt-3", sidebarCollapsed ? "px-2" : "px-3")}> <SidebarHeader className={cn("shrink-0 pb-2 pt-3", sidebarCollapsed ? "px-2" : "px-3")}>
<AccountHeader <AccountHeader
collapsed={sidebarCollapsed} collapsed={sidebarCollapsed}
name={me.data?.user.displayName || selectedMailbox?.address || "NewSzxcn"} name={me.data?.user.displayName || selectedMailbox?.address || "NewSzxcn"}
@@ -1352,7 +1352,7 @@ export function MailPage() {
</Button> </Button>
)} )}
</SidebarHeader> </SidebarHeader>
<SidebarContent className="px-1"> <SidebarContent className="min-h-0 overflow-x-hidden overflow-y-auto overscroll-contain px-1 pb-4">
{!sidebarCollapsed && publicSettings.isError && <SidebarQueryFailure label="邮箱设置读取失败" onRetry={() => { void publicSettings.refetch() }} />} {!sidebarCollapsed && publicSettings.isError && <SidebarQueryFailure label="邮箱设置读取失败" onRetry={() => { void publicSettings.refetch() }} />}
<SidebarGroup> <SidebarGroup>
{!sidebarCollapsed && ( {!sidebarCollapsed && (
@@ -1884,7 +1884,7 @@ export function MailPage() {
</SheetTrigger> </SheetTrigger>
<SheetContent side="left" className="w-[86vw] max-w-80 p-0 [&>button]:hidden" aria-describedby={undefined}> <SheetContent side="left" className="w-[86vw] max-w-80 p-0 [&>button]:hidden" aria-describedby={undefined}>
<SheetTitle className="sr-only"></SheetTitle> <SheetTitle className="sr-only"></SheetTitle>
<div className="h-svh">{sidebarContent}</div> <div className="h-svh min-h-0 overflow-hidden">{sidebarContent}</div>
</SheetContent> </SheetContent>
</Sheet> </Sheet>
<div className="min-w-0 flex-1 text-sm font-semibold">{mailView === "label" && selectedLabel ? <Badge variant="outline" className="gap-1.5 rounded-md font-normal"><span className="h-2 w-2 shrink-0 rounded-full" style={{ backgroundColor: labelDotColor(selectedLabel) }} />{selectedLabel.name}</Badge> : viewTitle}</div> <div className="min-w-0 flex-1 text-sm font-semibold">{mailView === "label" && selectedLabel ? <Badge variant="outline" className="gap-1.5 rounded-md font-normal"><span className="h-2 w-2 shrink-0 rounded-full" style={{ backgroundColor: labelDotColor(selectedLabel) }} />{selectedLabel.name}</Badge> : viewTitle}</div>
@@ -1900,7 +1900,7 @@ export function MailPage() {
</div> </div>
) : ( ) : (
<div className="mail-shell-grid h-full min-h-0 w-full min-w-0 overflow-hidden"> <div className="mail-shell-grid h-full min-h-0 w-full min-w-0 overflow-hidden">
<div className="min-w-0"> <div className="h-full min-h-0 min-w-0 overflow-hidden">
{sidebarContent} {sidebarContent}
</div> </div>
<section className="flex h-full min-h-0 min-w-0 flex-col"> <section className="flex h-full min-h-0 min-w-0 flex-col">
@@ -4286,15 +4286,15 @@ function ComposeDialog({ mailboxes, mailbox, open, draft, limits, canSend, canMa
<DialogFooter className="flex flex-row items-center justify-between gap-3 border-t bg-muted/15 px-4 py-3 sm:px-5"> <DialogFooter className="flex flex-row items-center justify-between gap-3 border-t bg-muted/15 px-4 py-3 sm:px-5">
{canSend && ( {canSend && (
<div className="flex min-w-0 items-center"> <div className="flex min-w-0 items-center">
<Button className={cn("min-h-10 px-4", canSchedule && "rounded-r-none")} disabled={send.isPending || !senderMailbox}><Send className="h-4 w-4" />{send.isPending ? "发送中..." : "发送"}</Button> <Button className={cn("compose-send-button min-h-10 px-4", canSchedule && "rounded-r-none")} disabled={send.isPending || !senderMailbox}><Send className="h-4 w-4" />{send.isPending ? "发送中..." : "发送"}</Button>
{canSchedule && ( {canSchedule && (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button type="button" size="icon" className="h-10 w-9 shrink-0 rounded-l-none border-l border-primary-foreground/25 px-0" title="发送选项" aria-label="发送选项" disabled={send.isPending || scheduleSend.isPending || !senderMailbox}> <Button type="button" size="icon" className="compose-send-button h-10 w-9 shrink-0 rounded-l-none border-l border-white/30 px-0" title="发送选项" aria-label="发送选项" disabled={send.isPending || scheduleSend.isPending || !senderMailbox}>
<ChevronDown className="h-4 w-4" /> <ChevronDown className="h-4 w-4" />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="start" side="top" className="w-40"> <DropdownMenuContent align="end" side="top" sideOffset={8} collisionPadding={12} className="w-[120px] min-w-[120px]">
<DropdownMenuItem className={composerMenuItemClass} onSelect={() => setScheduleDialogOpen(true)}><Clock3 className="h-4 w-4" /></DropdownMenuItem> <DropdownMenuItem className={composerMenuItemClass} onSelect={() => setScheduleDialogOpen(true)}><Clock3 className="h-4 w-4" /></DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>