Add support for dragging threads into composer as .eml attachments - #2800
Add support for dragging threads into composer as .eml attachments#2800bengotow wants to merge 2 commits into
Conversation
Dragging a thread out of the thread list and dropping it on an open composer now attaches it as a .eml file, the way Outlook and Gmail do. Thread rows already publish their ids on `mailspring-threads-data` for the folder-drop feature, so the composer's DropZone just had to learn to accept that type. The .eml itself is materialized on drop rather than on dragstart: fetching the raw RFC2822 source is a round trip to the sync engine, and `dragstart` has to populate dataTransfer synchronously. While the fetch is in flight the attachments area shows a placeholder. Dropping a thread onto a reply being composed inside that same thread is ignored — attaching a conversation to itself isn't useful. The staging logic (pick the thread's representative message, fetch it, write it to a temp file) was already duplicated between "Forward as Attachment" in the message list and the thread list context menu, so it moves into EmlUtils alongside defaultEmlFilename and all four call sites share it. Attachment names are unchanged for the existing features; dragged-in messages are named after their subject. Drafts are now excluded when picking a thread's representative message. They only exist locally, so the sync engine has no raw source to return for them — previously an unsent draft could be picked as the newest message in its thread and the export would silently produce nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQrDtxTjqZV5ms7W7Jp91m
|
Warning Indent Zero is shutting down on August 7th. Please migrate over to Indent 2.0 to continue getting PR reviews.
|
|
Three fixes from PR review on the drag-to-attach change.
Staged .eml files are now cleaned up. Staging moved from the old
`mailspring-fwd-${message.id}` path — bounded per message, so a repeat
forward overwrote it — to a randomly named directory, which grows without
limit. stageMessagesAsEml now removes the directories of messages whose
file never arrived, and discardStagedEml lets callers drop the rest once
they're done; the attachment store copies the file into its own directory
before addAttachment's onCreated fires, so that's the point where the
staged copy becomes garbage. It refuses any directory not named
`mailspring-eml-*`, so a stray path can't take a real directory with it.
A thread with nothing exportable in it is no longer reported as a failed
download. stageThreadsAsEml separates the two: a conversation holding
only unsent drafts is never fetched at all, so "Please try again" was
both wrong and unactionable. They now get distinct messages, and the
failure count no longer includes threads that were never fetched.
Dropping a thread onto a reply composed within that same thread is now
allowed. The payload isn't readable during dragEnter, so the drop cover
had already appeared by the time the id was filtered out — the drop
looked accepted and then did nothing. Outlook and Gmail both allow it,
and the guard was speculative.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQrDtxTjqZV5ms7W7Jp91m
|
All three review findings addressed in aabcf6c. Temp directory leak — valid, and a regression this PR introduced. The old path was Misleading download-failed dialog — valid. Same-thread drop swallowed silently — valid, but resolved by removing the guard rather than by adding a notice. The payload isn't readable during Spec suite: 1486 passing, 0 failing (up from 1480 — 6 new specs covering staging cleanup, the discard guard, and the unavailable-vs-failed split). Generated by Claude Code |
| } catch (err) { | ||
| return; | ||
| } | ||
| if (!threadIds.length) { |
There was a problem hiding this comment.
Functional · Self-thread drop guard removed
The self-drop guard that was here in the first commit (threadIds = threadIds.filter((id) => id !== this.props.draft.threadId);) was removed in aabcf6c and not replaced. For a reply draft, draft.threadId is set, so dragging that same thread onto the composer now stages its newest non-draft message and attaches the conversation to itself — the "almost always an accident" case the earlier commit deliberately suppressed, and which the PR description still lists ("Filters out drops of threads onto themselves"). If this removal was intentional, update the description; otherwise restore the filter. New-compose drafts have a null threadId, so they're unaffected either way.
Summary
This change enables users to drag threads from the thread list directly into the composer to attach them as .eml files. It introduces new utilities for exporting messages as RFC2822 .eml files and integrates them throughout the codebase.
Key Changes
New EML export utilities (
app/src/services/eml-utils.ts):newestExportableMessagesForThreadIds()- Resolves thread IDs to their most recent non-draft messagesstageMessagesAsEml()- Fetches raw RFC2822 source from sync engine and writes to temporary .eml files in isolated directoriesstageThreadsAsEml()- Convenience wrapper combining the above two functionsComposer drag-and-drop support (
app/internal_packages/composer/lib/composer-view.tsx):mailspring-threads-dataMIME type in drag eventsAttachments area UI enhancement (
app/internal_packages/composer/lib/attachments-area.tsx):attachingThreadCountprop to display loading spinner and message count while threads are being staged.attaching-messagesstyling for the placeholderRefactored existing forward-as-attachment flows:
app/internal_packages/thread-list/lib/thread-list-context-menu.ts- "Forward as Attachment" context menu now usesstageMessagesAsEml()app/internal_packages/message-list/lib/message-list.tsx- Forward message action now usesstageMessagesAsEml()Comprehensive test coverage (
app/spec/services/eml-utils-spec.ts):newestExportableMessagesForThreadIds()covering thread resolution, draft filtering, and edge casesstageMessagesAsEml()covering file staging, concurrent operations, partial failures, and custom filenamesImplementation Details
defaultEmlFilename()utility for generating clean, human-readable attachment names from message subjectshttps://claude.ai/code/session_01TQrDtxTjqZV5ms7W7Jp91m