feat: ask between a new message and Files for a file opened with the desktop app

This commit is contained in:
Linus Rath
2026-11-04 05:38:43 +01:00
parent e211ae905e
commit 1f4b0669ca
3 changed files with 24 additions and 2 deletions
+17
View File
@@ -114,6 +114,23 @@ describe('launches', () => {
expect(draft?.files.map((file) => file.name)).toEqual(['report.pdf']);
});
it('several files sent to the app go on one draft, without a question', async () => {
const { takeHandoff } = await import('@/lib/launch-handoff');
await run({ type: 'files', reason: 'attach', failed: [], files: [{ name: 'a.pdf', data: text('a') }, { name: 'b.png', data: text('b') }] });
expect(takeHandoff('compose')?.files.map((file) => file.name)).toEqual(['a.pdf', 'b.png']);
});
it('a file opened with the app asks: new message or Files, a new message first', async () => {
const { takeHandoff } = await import('@/lib/launch-handoff');
const { listenForFileChoices } = await import('@/lib/file-drop/choice');
const asked = vi.fn();
const stop = listenForFileChoices(asked);
await run({ type: 'files', reason: 'attach', ask: true, failed: [], files: [{ name: 'report.pdf', data: text('pdf') }] });
expect(asked).toHaveBeenCalledWith({ files: [expect.objectContaining({ name: 'report.pdf' })], preferred: 'new-message' });
expect(takeHandoff('compose')).toBeNull();
stop();
});
it('says so when a file could not be read', async () => {
await run({ type: 'files', reason: 'open', failed: ['gone.eml'], files: [] });
expect(notifyFailed).toHaveBeenCalledWith(['gone.eml']);
+5 -1
View File
@@ -9,6 +9,7 @@ import { isAppLocked } from '@/lib/desktop/app-lock-state';
import { getDesktopBridge } from '@/lib/desktop/bridge';
import type { DesktopOsCommand } from '@/lib/desktop/types';
import { openMailLink } from '@/lib/desktop/mail-links';
import { requestFileChoice } from '@/lib/file-drop/choice';
import { handleReminderCommand } from '@/lib/desktop/reminder-notifications';
import type { MailNotificationContext } from '@/lib/desktop/mail-notifications';
import { handOffDraft, handOffFiles, handOffNewMessage, kindForFile } from '@/lib/launch-handoff';
@@ -131,7 +132,10 @@ async function handleNotification(action: string, rawContext: string, reply: str
function handleFiles(command: Extract<DesktopOsCommand, { type: 'files' }>, env: OsCommandEnv): void {
const made = command.files.map(({ name, data }) => new File([new Uint8Array(data)], name));
const failed = [...command.failed];
if (command.reason === 'attach') {
if (command.reason === 'attach' && command.ask) {
// Opened with the app ("Open with"): the same choice as dropping it on the window.
requestFileChoice({ files: made, preferred: 'new-message' });
} else if (command.reason === 'attach') {
if (made.length > 0) handOffDraft({ files: made });
} else {
for (const file of made) {
+2 -1
View File
@@ -550,7 +550,8 @@ export type DesktopOsCommand =
| { type: 'open-mailbox'; mailboxId: string }
| { type: 'mailto'; url: string }
| { type: 'webcal'; url: string }
| { type: 'files'; reason: 'open' | 'attach'; files: DesktopOsFile[]; failed: string[] }
/** `ask`: opened with the app, so the user picks between a new message and Files. Without it, attach. */
| { type: 'files'; reason: 'open' | 'attach'; files: DesktopOsFile[]; failed: string[]; ask?: boolean }
| { type: 'notification'; action: string; context: string; reply?: string };
export interface DesktopOsNotification {