mirror of
https://github.com/root-fr/jmap-webmail.git
synced 2026-09-23 08:01:14 +00:00
Community PRs and targeted feature work. Highlights: Features - Archive now operates on the whole conversation, matching Gmail (#49) - Russian and Ukrainian locales, full translation sets (#59 @VsevolodSauta) - Custom favicon with unread badge painted over the base mail icon; also fixes stale sidebar counters on JMAP push (#63 @jabiinfante) - Optional domain-favicon avatars with a privacy-preserving proxy and a freemail denylist so the same provider logo isn't shown for every sender on a shared host (#22) Fixes - Print (Ctrl+P, button, menu) produces a clean single-column page for plain text and HTML emails alike, with actions/reply panels hidden, dark-mode overridden, and wide newsletters shrunk to fit A4 - Contacts now load past 500 entries by batching ContactCard/get to the server's maxObjectsInGet (#45 @capitanroy, #46) - Sieve filter destinations use the full Parent/Child folder path so Stalwart can resolve them (#62 @travier) - OIDC over plain HTTP shows a clear HTTPS-required banner instead of throwing crypto.subtle is undefined (#23 @jothoma1) - Missing contacts delete-confirm translation keys added to every locale (only Dutch had them before) - Favicon badge hook stopped tearing out Next.js's managed icon link, fixing a parentNode-is-null crash on route changes - Contact empty-state action buttons fit their container (#56) - Print layout excludes sidebar/list (#55 @prastowoagungwidodo) Docs - README example for OAUTH_ONLY=true to disable Basic Auth (#61 @travier) Infrastructure - Container images also published under jmap-webmail:1 for major-tag pinning (#57 @joelpurra, closes #54)
571 lines
13 KiB
CSS
571 lines
13 KiB
CSS
@import "tailwindcss";
|
|
|
|
@custom-variant dark (&:where(.dark, .dark *));
|
|
|
|
:root {
|
|
--color-border: #e2e8f0;
|
|
--color-input: #e2e8f0;
|
|
--color-ring: #94a3b8;
|
|
--color-background: #ffffff;
|
|
--color-foreground: #0f172a;
|
|
--color-primary: #3b82f6;
|
|
--color-primary-foreground: #ffffff;
|
|
--color-secondary: #f8fafc;
|
|
--color-secondary-foreground: #0f172a;
|
|
--color-muted: #f1f5f9;
|
|
--color-muted-foreground: #64748b;
|
|
--color-accent: #dbeafe;
|
|
--color-accent-foreground: #1e40af;
|
|
--color-destructive: #dc2626;
|
|
--color-destructive-foreground: #ffffff;
|
|
|
|
/* Settings variables */
|
|
--font-size-base: 16px;
|
|
--list-item-height: 48px;
|
|
--transition-duration: 0.2s;
|
|
}
|
|
|
|
.dark {
|
|
--color-border: #262626;
|
|
--color-input: #262626;
|
|
--color-ring: #d4d4d4;
|
|
--color-background: #0a0a0a;
|
|
--color-foreground: #fafafa;
|
|
--color-primary: #fafafa;
|
|
--color-primary-foreground: #171717;
|
|
--color-secondary: #262626;
|
|
--color-secondary-foreground: #fafafa;
|
|
--color-muted: #262626;
|
|
--color-muted-foreground: #a3a3a3;
|
|
--color-accent: #1e3a8a;
|
|
--color-accent-foreground: #dbeafe;
|
|
--color-destructive: #ef4444;
|
|
--color-destructive-foreground: #ffffff;
|
|
}
|
|
|
|
@theme inline {
|
|
--color-border: var(--color-border);
|
|
--color-input: var(--color-input);
|
|
--color-ring: var(--color-ring);
|
|
--color-background: var(--color-background);
|
|
--color-foreground: var(--color-foreground);
|
|
--color-primary: var(--color-primary);
|
|
--color-primary-foreground: var(--color-primary-foreground);
|
|
--color-secondary: var(--color-secondary);
|
|
--color-secondary-foreground: var(--color-secondary-foreground);
|
|
--color-muted: var(--color-muted);
|
|
--color-muted-foreground: var(--color-muted-foreground);
|
|
--color-accent: var(--color-accent);
|
|
--color-accent-foreground: var(--color-accent-foreground);
|
|
--color-destructive: var(--color-destructive);
|
|
--color-destructive-foreground: var(--color-destructive-foreground);
|
|
}
|
|
|
|
* {
|
|
border-color: var(--color-border);
|
|
}
|
|
|
|
body {
|
|
background-color: var(--color-background);
|
|
color: var(--color-foreground);
|
|
font-family: system-ui, -apple-system, sans-serif;
|
|
font-size: var(--font-size-base);
|
|
font-feature-settings: "rlig" 1, "calt" 1;
|
|
}
|
|
|
|
.btn-destructive {
|
|
background-color: var(--color-destructive);
|
|
color: var(--color-destructive-foreground);
|
|
}
|
|
|
|
.btn-destructive:hover {
|
|
background-color: color-mix(in srgb, var(--color-destructive) 90%, black);
|
|
}
|
|
|
|
/* Minimalist scrollbar */
|
|
::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
|
|
::-webkit-scrollbar-track {
|
|
background-color: transparent;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
background-color: var(--color-border);
|
|
border-radius: 9999px;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background-color: var(--color-muted-foreground);
|
|
}
|
|
|
|
/* Enhanced Email Content Styling */
|
|
|
|
.email-content-wrapper {
|
|
min-width: 100%;
|
|
}
|
|
|
|
.email-content {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
font-size: 0.9375rem;
|
|
line-height: 1.6;
|
|
color: var(--color-foreground);
|
|
max-width: none;
|
|
overflow-wrap: break-word;
|
|
word-wrap: break-word;
|
|
}
|
|
|
|
.email-content p {
|
|
margin: 0.75rem 0;
|
|
}
|
|
|
|
.email-content a {
|
|
color: var(--color-primary);
|
|
text-decoration: none;
|
|
transition: color 0.2s;
|
|
}
|
|
|
|
.email-content a:hover {
|
|
text-decoration: underline;
|
|
color: var(--color-accent-foreground);
|
|
}
|
|
|
|
.email-content img {
|
|
max-width: 100%;
|
|
height: auto;
|
|
border-radius: 0.375rem;
|
|
margin: 1rem 0;
|
|
display: block;
|
|
}
|
|
|
|
.email-content blockquote {
|
|
border-left: 3px solid #d1d5db;
|
|
padding-left: 1rem;
|
|
margin: 1rem 0;
|
|
color: #4b5563;
|
|
font-style: italic;
|
|
}
|
|
|
|
.dark .email-content blockquote {
|
|
border-left-color: #4b5563;
|
|
color: #9ca3af;
|
|
}
|
|
|
|
.email-content pre {
|
|
background-color: #f5f5f7;
|
|
border: 1px solid #e5e7eb;
|
|
border-radius: 0.375rem;
|
|
padding: 1rem;
|
|
font-family: 'SF Mono', Monaco, Menlo, Consolas, monospace;
|
|
font-size: 0.875rem;
|
|
overflow-x: auto;
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
.dark .email-content pre {
|
|
background-color: #1f2937;
|
|
border-color: #374151;
|
|
}
|
|
|
|
.email-content code {
|
|
background-color: #f3f4f6;
|
|
padding: 0.125rem 0.375rem;
|
|
border-radius: 0.25rem;
|
|
font-family: 'SF Mono', Monaco, Menlo, Consolas, monospace;
|
|
font-size: 0.875rem;
|
|
color: #dc2626;
|
|
}
|
|
|
|
.dark .email-content code {
|
|
background-color: #374151;
|
|
color: #f87171;
|
|
}
|
|
|
|
.email-content h1,
|
|
.email-content h2,
|
|
.email-content h3,
|
|
.email-content h4 {
|
|
margin-top: 1.5rem;
|
|
margin-bottom: 0.75rem;
|
|
font-weight: 600;
|
|
line-height: 1.25;
|
|
color: var(--color-foreground);
|
|
}
|
|
|
|
.email-content h1 { font-size: 1.5rem; }
|
|
.email-content h2 { font-size: 1.25rem; }
|
|
.email-content h3 { font-size: 1.125rem; }
|
|
.email-content h4 { font-size: 1rem; }
|
|
|
|
.email-content ul,
|
|
.email-content ol {
|
|
margin: 1rem 0;
|
|
padding-left: 1.75rem;
|
|
}
|
|
|
|
.email-content li {
|
|
margin: 0.375rem 0;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.email-content ul li {
|
|
list-style-type: disc;
|
|
}
|
|
|
|
.email-content ol li {
|
|
list-style-type: decimal;
|
|
}
|
|
|
|
/* Only style tables that are actual data tables, not layout tables */
|
|
.email-content table.data-table,
|
|
.email-content table[border="1"] {
|
|
border-collapse: collapse;
|
|
width: 100%;
|
|
margin: 1rem 0;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.email-content table.data-table th,
|
|
.email-content table.data-table td,
|
|
.email-content table[border="1"] th,
|
|
.email-content table[border="1"] td {
|
|
padding: 0.625rem;
|
|
border: 1px solid #e5e7eb;
|
|
text-align: left;
|
|
}
|
|
|
|
.email-content table.data-table th,
|
|
.email-content table[border="1"] th {
|
|
background-color: #f9fafb;
|
|
font-weight: 600;
|
|
color: #374151;
|
|
}
|
|
|
|
.dark .email-content table.data-table th,
|
|
.dark .email-content table[border="1"] th {
|
|
background-color: #1f2937;
|
|
color: #d1d5db;
|
|
}
|
|
|
|
.dark .email-content table.data-table td,
|
|
.dark .email-content table[border="1"] td {
|
|
border-color: #374151;
|
|
}
|
|
|
|
/* Reset styles for layout tables (commonly used in HTML emails) */
|
|
.email-content table {
|
|
border-collapse: collapse;
|
|
border-spacing: 0;
|
|
}
|
|
|
|
/* Let HTML email's own styles take precedence */
|
|
.email-content table:not(.data-table):not([border="1"]) {
|
|
border: initial;
|
|
}
|
|
|
|
.email-content table:not(.data-table):not([border="1"]) td,
|
|
.email-content table:not(.data-table):not([border="1"]) th {
|
|
border: initial;
|
|
padding: initial;
|
|
}
|
|
|
|
.email-content hr {
|
|
border: none;
|
|
border-top: 1px solid #e5e7eb;
|
|
margin: 1.5rem 0;
|
|
}
|
|
|
|
.dark .email-content hr {
|
|
border-top-color: #374151;
|
|
}
|
|
|
|
/* Email thread quoted text */
|
|
.email-content .quoted-text {
|
|
border-left: 3px solid #d1d5db;
|
|
padding-left: 1rem;
|
|
margin: 1rem 0;
|
|
color: #4b5563;
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.dark .email-content .quoted-text {
|
|
border-left-color: #4b5563;
|
|
color: #9ca3af;
|
|
}
|
|
|
|
/* Toast animations */
|
|
@keyframes slide-in {
|
|
from {
|
|
transform: translateX(100%);
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
transform: translateX(0);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.animate-slide-in {
|
|
animation: slide-in 0.3s ease-out;
|
|
}
|
|
|
|
|
|
/* Mobile Responsive Utilities */
|
|
|
|
/* Safe area insets for notched devices (iPhone X+, etc.) */
|
|
.safe-area-inset-top {
|
|
padding-top: env(safe-area-inset-top);
|
|
}
|
|
|
|
.safe-area-inset-bottom {
|
|
padding-bottom: env(safe-area-inset-bottom);
|
|
}
|
|
|
|
.safe-area-inset-left {
|
|
padding-left: env(safe-area-inset-left);
|
|
}
|
|
|
|
.safe-area-inset-right {
|
|
padding-right: env(safe-area-inset-right);
|
|
}
|
|
|
|
/* Minimum touch targets (44x44px recommended by Apple HIG) */
|
|
.touch-target {
|
|
min-height: 44px;
|
|
min-width: 44px;
|
|
}
|
|
|
|
/* Prevent text selection on mobile for UI elements */
|
|
.no-select {
|
|
-webkit-user-select: none;
|
|
user-select: none;
|
|
-webkit-touch-callout: none;
|
|
}
|
|
|
|
/* Smooth transitions for view switching */
|
|
.view-transition {
|
|
transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
|
|
}
|
|
|
|
/* Hide scrollbar on mobile while keeping functionality */
|
|
@media (max-width: 767px) {
|
|
.mobile-scroll-hidden::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
.mobile-scroll-hidden {
|
|
-ms-overflow-style: none;
|
|
scrollbar-width: none;
|
|
}
|
|
}
|
|
|
|
/* Mobile backdrop blur support */
|
|
@supports (backdrop-filter: blur(8px)) {
|
|
.mobile-backdrop {
|
|
backdrop-filter: blur(8px);
|
|
-webkit-backdrop-filter: blur(8px);
|
|
}
|
|
}
|
|
|
|
/* Prevent overscroll bounce on iOS */
|
|
.no-overscroll {
|
|
overscroll-behavior: none;
|
|
}
|
|
|
|
/* Slide in from right animation (for mobile views) */
|
|
@keyframes slide-in-from-right {
|
|
from {
|
|
transform: translateX(100%);
|
|
}
|
|
to {
|
|
transform: translateX(0);
|
|
}
|
|
}
|
|
|
|
.animate-slide-in-from-right {
|
|
animation: slide-in-from-right 0.3s ease-out;
|
|
}
|
|
|
|
/* Slide in from left animation (for sidebar) */
|
|
@keyframes slide-in-from-left {
|
|
from {
|
|
transform: translateX(-100%);
|
|
}
|
|
to {
|
|
transform: translateX(0);
|
|
}
|
|
}
|
|
|
|
.animate-slide-in-from-left {
|
|
animation: slide-in-from-left 0.3s ease-out;
|
|
}
|
|
|
|
/* Extra-compact density: enforce minimum touch target on touch devices */
|
|
@media (pointer: coarse) {
|
|
[data-density="extra-compact"] .email-list-item {
|
|
min-height: 44px;
|
|
}
|
|
}
|
|
|
|
/* Reduced motion: respect user OS preference */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
*,
|
|
*::before,
|
|
*::after {
|
|
animation-duration: 0.01ms !important;
|
|
animation-iteration-count: 1 !important;
|
|
transition-duration: 0.01ms !important;
|
|
scroll-behavior: auto !important;
|
|
}
|
|
}
|
|
|
|
/* Disable backdrop-filter on mobile for performance */
|
|
@media (max-width: 767px) {
|
|
.mobile-backdrop {
|
|
backdrop-filter: none !important;
|
|
-webkit-backdrop-filter: none !important;
|
|
}
|
|
}
|
|
|
|
/* Print support. The user clicks Print on an email, which clones the
|
|
viewer into #print-overlay at the body level and flips html.is-printing
|
|
on. The live app is hidden, the overlay is positioned at top-left and
|
|
styled for paper: black text, transparent-over-white background,
|
|
no shadows, no animations. Keeping the overlay at the body root
|
|
avoids cross-browser :has()/flex/visibility quirks entirely.
|
|
Cleanup runs on the afterprint event. */
|
|
html.is-printing body > *:not(#print-overlay) {
|
|
display: none !important;
|
|
}
|
|
|
|
#print-overlay {
|
|
position: absolute;
|
|
left: -10000px;
|
|
top: -10000px;
|
|
width: 1px;
|
|
height: 1px;
|
|
overflow: hidden;
|
|
pointer-events: none;
|
|
}
|
|
|
|
@media print {
|
|
@page {
|
|
margin: 12mm;
|
|
}
|
|
|
|
html, body {
|
|
background: white !important;
|
|
color: #000 !important;
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
height: auto !important;
|
|
min-height: 0 !important;
|
|
overflow: visible !important;
|
|
}
|
|
|
|
html.is-printing #print-overlay {
|
|
position: static !important;
|
|
left: auto !important;
|
|
top: auto !important;
|
|
width: auto !important;
|
|
height: auto !important;
|
|
overflow: visible !important;
|
|
}
|
|
|
|
#print-overlay,
|
|
#print-overlay * {
|
|
color: #000 !important;
|
|
background: transparent !important;
|
|
background-color: transparent !important;
|
|
background-image: none !important;
|
|
box-shadow: none !important;
|
|
text-shadow: none !important;
|
|
border-color: #ccc !important;
|
|
animation: none !important;
|
|
transition: none !important;
|
|
transform: none !important;
|
|
filter: none !important;
|
|
opacity: 1 !important;
|
|
visibility: visible !important;
|
|
}
|
|
|
|
#print-overlay #email-viewer-container {
|
|
position: static !important;
|
|
display: block !important;
|
|
width: 100% !important;
|
|
height: auto !important;
|
|
min-height: 0 !important;
|
|
max-height: none !important;
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
overflow: visible !important;
|
|
background: #fff !important;
|
|
box-shadow: none !important;
|
|
border: none !important;
|
|
}
|
|
|
|
#print-overlay #email-content-area {
|
|
display: block !important;
|
|
width: 100% !important;
|
|
height: auto !important;
|
|
max-height: none !important;
|
|
overflow: visible !important;
|
|
}
|
|
|
|
#print-overlay iframe {
|
|
height: auto !important;
|
|
min-height: 0 !important;
|
|
max-height: none !important;
|
|
width: 100% !important;
|
|
background: #fff !important;
|
|
}
|
|
|
|
/* Newsletter-style HTML emails are typically laid out with fixed-width
|
|
tables (width="600" or style="width:640px"). On A4 paper the usable
|
|
width is around 180mm, so the right edge of the content gets clipped.
|
|
Force every printed element to shrink to the page width, let tables
|
|
auto-layout, and encourage long URLs / words to break. */
|
|
#print-overlay *:not(html):not(body) {
|
|
max-width: 100% !important;
|
|
box-sizing: border-box !important;
|
|
}
|
|
#print-overlay img,
|
|
#print-overlay video,
|
|
#print-overlay canvas,
|
|
#print-overlay svg {
|
|
max-width: 100% !important;
|
|
height: auto !important;
|
|
}
|
|
#print-overlay table {
|
|
max-width: 100% !important;
|
|
width: auto !important;
|
|
table-layout: auto !important;
|
|
}
|
|
#print-overlay td,
|
|
#print-overlay th,
|
|
#print-overlay p,
|
|
#print-overlay li,
|
|
#print-overlay pre,
|
|
#print-overlay code {
|
|
overflow-wrap: anywhere !important;
|
|
word-break: break-word !important;
|
|
}
|
|
#print-overlay pre {
|
|
white-space: pre-wrap !important;
|
|
}
|
|
#print-overlay .print-inlined-iframe {
|
|
width: 100% !important;
|
|
}
|
|
|
|
#print-overlay a {
|
|
color: #0645ad !important;
|
|
text-decoration: underline !important;
|
|
}
|
|
|
|
#print-overlay .print\:hidden {
|
|
display: none !important;
|
|
}
|
|
}
|