attachments preview overlay, docx/xlsx/odf rendering, composer drag-drop & undo-send UX
Frontend: - Full-screen attachment preview overlay (AttachmentPreview) for image/pdf/txt plus office docs: .docx via docx-preview (rendered live into the sandboxed iframe so header/footer VML images survive), .xlsx via xlsx-preview, and .odt/.ods/.odp/.odg via a JSZip+DOMParser ODF walker (officeConvert). - Type-aware attachment icons (green spreadsheet, blue document, red PDF, image/archive); new document/image/spreadsheet/print SVGs. - Composer: drag-drop attachments (body drop inserts images inline, else attaches); inserted images are selectable/resizable/deletable (RichTextEditor). - Undo-send: "Sending…" banner with live "N seconds to Undo" countdown. - Search result list: pin/archive/delete update correctly; grey-out only for search hits; scoped "Search in Trash/Spam". Backend: - Search: prefix (search-as-you-type) matching, email-address search, and address-first result ordering; Trash/Junk excluded with scoped folder search. - Attachments: RFC 6266 Content-Disposition for non-ASCII filenames; one-time inline image loading. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rcj3TpydMTzTSmU7kunzf8
This commit is contained in:
@@ -61,6 +61,7 @@ export function MessageList({
|
||||
onDeletePermanent,
|
||||
onTogglePin,
|
||||
checkedIds,
|
||||
dimmedIds,
|
||||
onToggleCheck,
|
||||
}: {
|
||||
threads: ThreadSummary[];
|
||||
@@ -73,6 +74,7 @@ export function MessageList({
|
||||
onDeletePermanent: (id: string) => void;
|
||||
onTogglePin: (id: string, pinned: boolean) => void;
|
||||
checkedIds: Set<string>;
|
||||
dimmedIds?: Set<string>;
|
||||
onToggleCheck: (id: string) => void;
|
||||
}) {
|
||||
const t = useT();
|
||||
@@ -103,6 +105,7 @@ export function MessageList({
|
||||
const unread = thread.unread_count > 0;
|
||||
const selected = selectedId === thread.id;
|
||||
const checked = checkedIds.has(thread.id);
|
||||
const dimmed = dimmedIds?.has(thread.id) ?? false;
|
||||
const primary = thread.participants[0];
|
||||
const names =
|
||||
thread.participants.map((p) => p.name || p.email).join(", ") || "(no one)";
|
||||
@@ -116,10 +119,11 @@ export function MessageList({
|
||||
<div
|
||||
key={thread.id}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => onSelect(thread.id)}
|
||||
tabIndex={dimmed ? -1 : 0}
|
||||
aria-disabled={dimmed}
|
||||
onClick={() => !dimmed && onSelect(thread.id)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") onSelect(thread.id);
|
||||
if (!dimmed && (e.key === "Enter" || e.key === " ")) onSelect(thread.id);
|
||||
}}
|
||||
style={{
|
||||
position: "absolute",
|
||||
@@ -129,7 +133,9 @@ export function MessageList({
|
||||
height: item.size,
|
||||
transform: `translateY(${item.start}px)`,
|
||||
}}
|
||||
className="group absolute inset-x-0 cursor-pointer px-2 pt-[7px]"
|
||||
className={`group absolute inset-x-0 px-2 pt-[7px] ${
|
||||
dimmed ? "pointer-events-none opacity-40" : "cursor-pointer"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`flex h-full items-center gap-3 rounded-lg px-3 ${
|
||||
|
||||
Reference in New Issue
Block a user