feat(webmail): a better reader and a drawn logo
The mark is rebuilt as a lambda of two square-cut strokes of one weight, joined without a seam, on a flat ultramarine tile with continuous corners: the rounded strokes and the gradient are gone. A message written plainly is now drawn in the app's own colours instead of a white slab, with text too dark for the ground lifted and white backdrops cleared; a designed one (newsletter, receipt) keeps its white sheet. A reply's quoted history folds behind a button. A narrower reading column, a larger title, and a way to see a plain message as sent. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9e50b47b5d
commit
fece870d49
17 files changed
+336
-77
No files matched your search
@@ -30,6 +30,7 @@ import {
|
||||
} from "../../../../../lib/mailCounts";
|
||||
import { plainSnippet } from "../../../../../lib/snippet";
|
||||
import { useTrustedSenders } from "../../../../../lib/trustedSenders";
|
||||
import { useTheme } from "../../../../../lib/theme";
|
||||
import type { InlineImages } from "../../../../../lib/emailBody";
|
||||
import { messageDocument, hasBlockedRemoteContent } from "../../../../../lib/renderMessage";
|
||||
import {
|
||||
@@ -158,6 +159,9 @@ export default function MailFolderPage({ params }: { params: Promise<{ folder: s
|
||||
|
||||
const [messages, setMessages] = useState<MessageSummary[]>([]);
|
||||
const [selected, setSelected] = useState<RenderedMessage | null>(null);
|
||||
// A plainly written message takes the app's colours; this shows it as sent.
|
||||
const [showOriginal, setShowOriginal] = useState(false);
|
||||
const { theme } = useTheme();
|
||||
// The open conversation, when the list is showing conversations.
|
||||
const [selectedThread, setSelectedThread] = useState<MessageSummary | null>(null);
|
||||
const conversations = useConversationView();
|
||||
@@ -444,6 +448,7 @@ export default function MailFolderPage({ params }: { params: Promise<{ folder: s
|
||||
}
|
||||
}
|
||||
setLoadRemoteImages(false);
|
||||
setShowOriginal(false);
|
||||
setReaderHeight(420);
|
||||
try {
|
||||
const res = await fetch(`/api/v1/mail/message/${uid}?folder=${encodeURIComponent(folder)}`, {
|
||||
@@ -478,13 +483,13 @@ export default function MailFolderPage({ params }: { params: Promise<{ folder: s
|
||||
* markup, then resolve the message's own inline parts, then block or restore
|
||||
* remote sources - which is the only part the reader chooses.
|
||||
*/
|
||||
const readerDocument = useMemo(() => {
|
||||
const readerBody = useMemo(() => {
|
||||
if (!selected) return null;
|
||||
// A trusted sender's images load without asking; anyone else's wait for
|
||||
// the reader's choice, one message at a time.
|
||||
const showImages = loadRemoteImages || trustedSenders.isTrusted(selected.from ?? "");
|
||||
return messageDocument(selected, showImages);
|
||||
}, [selected, loadRemoteImages, trustedSenders]);
|
||||
return messageDocument(selected, showImages, { theme, original: showOriginal });
|
||||
}, [selected, loadRemoteImages, trustedSenders, theme, showOriginal]);
|
||||
|
||||
// Whether anything was actually held back, so the privacy banner is not shown
|
||||
// over a message that has no remote content at all.
|
||||
@@ -794,7 +799,11 @@ export default function MailFolderPage({ params }: { params: Promise<{ folder: s
|
||||
name: target.name,
|
||||
label: FOLDER_LABELS[(target.special_use || target.name).toLowerCase()] ?? target.name,
|
||||
}))}
|
||||
document={readerDocument}
|
||||
document={readerBody?.document ?? null}
|
||||
paper={readerBody?.paper ?? true}
|
||||
canAdapt={readerBody?.canAdapt ?? false}
|
||||
showingOriginal={showOriginal}
|
||||
onToggleOriginal={() => setShowOriginal((v) => !v)}
|
||||
frameHeight={readerHeight}
|
||||
hasBlockedContent={hasBlockedContent}
|
||||
imagesLoaded={loadRemoteImages || senderTrusted}
|
||||
|
||||
Reference in new issue
Block a user