Files
webmail/lib/file-drop/read.ts
T
Linus Rath e211ae905e feat: offer attach or upload when files are dragged onto any screen
Files dragged into the window from outside now bring up a full-window
overlay with large drop zones: attach to a new message and upload to
Files, with the open composer first, "Upload to <folder>" on the Files
screen, and add to calendar, add to contacts, open or import for .ics,
.vcf and .eml files. At most four zones, the one under the pointer
highlights, Esc or dragging out cancels, a drop beside the zones does
nothing. Drags that start in the app never show it. A file pasted
outside a text field gets the same choices as a keyboard dialog.

The desktop app used to turn any file dropped on the window into a new
draft (lib/desktop/window-files.ts). That handling moved to the
overlay, so the "files on the window" tests in os-window.test.tsx went
with it: the same cases are now in components/file-drop/__tests__.
2026-11-04 05:37:35 +01:00

31 lines
1.3 KiB
TypeScript

/**
* The files of a drop, read while the drop event lasts. A dropped folder only shows up
* through the entry API, and only Files can take one: its upload keeps the folder's
* layout. Everywhere else a folder is left out.
*/
import { getDroppedFilesAndFolders } from '@/lib/webdav/drop-utils';
export interface DroppedFiles {
/** Files at the top of the drop, no folders. */
plain: File[];
/** Everything, folders walked, with their paths: for Files. */
all: File[];
folders: boolean;
}
/** Call from the drop handler itself: the browser empties the transfer once it returns. */
export function readDroppedFiles(transfer: DataTransfer): Promise<DroppedFiles> {
const items = Array.from(transfer.items ?? []);
const entries = items.map((item) => (typeof item.webkitGetAsEntry === 'function' ? item.webkitGetAsEntry() : null));
const plainNow = items.flatMap((item, index) => {
if (item.kind !== 'file' || entries[index]?.isDirectory) return [];
const file = item.getAsFile();
return file ? [file] : [];
});
const plain = items.length > 0 ? plainNow : Array.from(transfer.files ?? []);
const folders = entries.some((entry) => entry?.isDirectory);
if (!folders) return Promise.resolve({ plain, all: plain, folders: false });
return getDroppedFilesAndFolders(transfer).then(({ files }) => ({ plain, all: files, folders: true }));
}