feat: customize NodeSeek-style webmail
This commit is contained in:
+62
-23
@@ -5,39 +5,46 @@
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--foreground: 222 47% 11%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
--card-foreground: 222 47% 11%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
--primary: 240 5.9% 10%;
|
||||
--popover-foreground: 222 47% 11%;
|
||||
--primary: 224 44% 12%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--secondary: 213 37% 96%;
|
||||
--secondary-foreground: 222 47% 11%;
|
||||
--muted: 213 37% 96%;
|
||||
--muted-foreground: 216 22% 42%;
|
||||
--accent: 213 37% 94%;
|
||||
--accent-foreground: 222 47% 11%;
|
||||
--destructive: 358 88% 61%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
--ring: 240 5.9% 10%;
|
||||
--border: 214 32% 90%;
|
||||
--input: 214 32% 86%;
|
||||
--ring: 216 22% 42%;
|
||||
--radius: 0.5rem;
|
||||
--sidebar-background: 0 0% 98%;
|
||||
--sidebar-foreground: 240 5.3% 26.1%;
|
||||
--sidebar-primary: 240 5.9% 10%;
|
||||
--sidebar-background: 0 0% 100%;
|
||||
--sidebar-foreground: 222 47% 11%;
|
||||
--sidebar-primary: 224 44% 12%;
|
||||
--sidebar-primary-foreground: 0 0% 98%;
|
||||
--sidebar-accent: 240 4.8% 95.9%;
|
||||
--sidebar-accent-foreground: 240 5.9% 10%;
|
||||
--sidebar-border: 220 13% 91%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
--sidebar-accent: 213 37% 94%;
|
||||
--sidebar-accent-foreground: 222 47% 11%;
|
||||
--sidebar-border: 214 32% 90%;
|
||||
--sidebar-ring: 216 22% 42%;
|
||||
}
|
||||
|
||||
* { @apply border-border; }
|
||||
body { @apply bg-background text-foreground antialiased; }
|
||||
html {
|
||||
color-scheme: light;
|
||||
font-size: 15px;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground antialiased;
|
||||
font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
}
|
||||
html, body, #root { min-height: 100%; }
|
||||
html { color-scheme: light; }
|
||||
html.dark { color-scheme: dark; }
|
||||
|
||||
.dark {
|
||||
@@ -71,6 +78,38 @@
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
[data-sidebar="content"] {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
[data-sidebar="group"] {
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
|
||||
[data-sidebar="menu"] {
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
[data-sidebar="menu-button"] {
|
||||
color: hsl(var(--sidebar-foreground));
|
||||
}
|
||||
|
||||
[data-sidebar="menu-button"] svg {
|
||||
color: hsl(var(--muted-foreground));
|
||||
stroke-width: 1.8;
|
||||
}
|
||||
|
||||
[data-sidebar="menu-button"][data-active="true"] {
|
||||
background: hsl(var(--sidebar-accent));
|
||||
color: hsl(var(--sidebar-accent-foreground));
|
||||
}
|
||||
|
||||
[data-sidebar="menu-button"][data-active="true"] svg {
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
}
|
||||
|
||||
html.theme-transition body {
|
||||
transition: background-color 180ms ease, color 180ms ease;
|
||||
}
|
||||
|
||||
+47
-8
@@ -4,6 +4,44 @@ export * from "./api-types"
|
||||
const REQUEST_TIMEOUT_MS = 15_000
|
||||
const MAIL_DELIVERY_TIMEOUT_MS = 60_000
|
||||
|
||||
export type MailSearchParams = {
|
||||
q?: string
|
||||
from?: string
|
||||
to?: string
|
||||
subject?: string
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
attachmentMode?: "all" | "with" | "without"
|
||||
minSizeKb?: string
|
||||
maxSizeKb?: string
|
||||
readStatus?: "all" | "read" | "unread"
|
||||
flagStatus?: "all" | "starred" | "unstarred"
|
||||
hasAttachments?: boolean
|
||||
unread?: boolean
|
||||
starred?: boolean
|
||||
}
|
||||
|
||||
function appendMailSearchParams(params: URLSearchParams, search: MailSearchParams | string) {
|
||||
if (typeof search === "string") {
|
||||
if (search) params.set("q", search)
|
||||
return
|
||||
}
|
||||
if (search.q) params.set("q", search.q)
|
||||
if (search.from) params.set("from", search.from)
|
||||
if (search.to) params.set("to", search.to)
|
||||
if (search.subject) params.set("subject", search.subject)
|
||||
if (search.startDate) params.set("startDate", search.startDate)
|
||||
if (search.endDate) params.set("endDate", search.endDate)
|
||||
if (search.attachmentMode && search.attachmentMode !== "all") params.set("attachmentMode", search.attachmentMode)
|
||||
else if (search.hasAttachments) params.set("hasAttachments", "1")
|
||||
if (search.minSizeKb) params.set("minSizeKb", search.minSizeKb)
|
||||
if (search.maxSizeKb) params.set("maxSizeKb", search.maxSizeKb)
|
||||
if (search.readStatus && search.readStatus !== "all") params.set("readStatus", search.readStatus)
|
||||
else if (search.unread) params.set("unread", "1")
|
||||
if (search.flagStatus && search.flagStatus !== "all") params.set("flagStatus", search.flagStatus)
|
||||
else if (search.starred) params.set("starred", "1")
|
||||
}
|
||||
|
||||
async function request<T>(path: string, init: RequestInit & { timeoutMs?: number } = {}): Promise<T> {
|
||||
const { timeoutMs, ...requestInit } = init
|
||||
const controller = new AbortController()
|
||||
@@ -152,18 +190,21 @@ export const api = {
|
||||
return request<MailLabel>(`/api/mail/labels${query}`, { method: "POST", body: JSON.stringify({ name: payload.name, color: payload.color || "" }) })
|
||||
},
|
||||
deleteLabel: (id: string, mailboxId?: string) => request<{ labels: MailLabel[] }>(`/api/mail/labels/${id}${mailboxId ? `?mailboxId=${encodeURIComponent(mailboxId)}` : ""}`, { method: "DELETE" }),
|
||||
messages: (folder: string, q = "", cursor = "", mailboxId?: string) => {
|
||||
const params = new URLSearchParams({ folder, q, cursor })
|
||||
messages: (folder: string, search: MailSearchParams | string = "", cursor = "", mailboxId?: string) => {
|
||||
const params = new URLSearchParams({ folder, cursor })
|
||||
appendMailSearchParams(params, search)
|
||||
if (mailboxId) params.set("mailboxId", mailboxId)
|
||||
return request<ListResponse<MailMessage>>(`/api/mail/messages?${params.toString()}`)
|
||||
},
|
||||
labelMessages: (labelId: string, q = "", cursor = "", mailboxId?: string) => {
|
||||
const params = new URLSearchParams({ labelId, q, cursor })
|
||||
labelMessages: (labelId: string, search: MailSearchParams | string = "", cursor = "", mailboxId?: string) => {
|
||||
const params = new URLSearchParams({ labelId, cursor })
|
||||
appendMailSearchParams(params, search)
|
||||
if (mailboxId) params.set("mailboxId", mailboxId)
|
||||
return request<ListResponse<MailMessage>>(`/api/mail/messages?${params.toString()}`)
|
||||
},
|
||||
starredMessages: (q = "", cursor = "", mailboxId?: string) => {
|
||||
const params = new URLSearchParams({ q, cursor })
|
||||
starredMessages: (search: MailSearchParams | string = "", cursor = "", mailboxId?: string) => {
|
||||
const params = new URLSearchParams({ cursor })
|
||||
appendMailSearchParams(params, search)
|
||||
if (mailboxId) params.set("mailboxId", mailboxId)
|
||||
return request<ListResponse<MailMessage>>(`/api/mail/starred?${params.toString()}`)
|
||||
},
|
||||
@@ -199,5 +240,3 @@ export const api = {
|
||||
delete: (id: string) => request<{ ok: boolean }>(`/api/mail/messages/${id}`, { method: "DELETE" }),
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+499
-164
File diff suppressed because it is too large
Load Diff
+1104
-130
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user