diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-24 12:41:46 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-24 12:41:46 +0200 |
| commit | 58f13ad9d78a07aab1d683462834a2493078744d (patch) | |
| tree | bd2273b161b214b986c11a8d00d3e20138c9f47b /src/types.h | |
| parent | b0d612c8ea232674ac0734b1121cfd0bb50d532b (diff) | |
| download | qtmaildir-58f13ad9d78a07aab1d683462834a2493078744d.tar.gz qtmaildir-58f13ad9d78a07aab1d683462834a2493078744d.zip | |
feat(compose): open a draft to finish it
Item 153. DraftStore had a write() and no reader, and nothing opened a
composer from an existing message, so a draft rendered like ordinary mail
and could never be finished or sent.
ComposeContextBuilder::forDraft() reads one back. A new Kind::Draft seeds
every field verbatim: the subject takes no Re:/Fwd: prefix, and the body
goes in exactly as it was left, with none of seedBody()'s quote framing. It
is reachable by double-click and by an edit_draft action in the Message
menu.
Three things the shape of this depends on.
A resumed draft must OWN its file. Maildir has no in-place edit, so an
autosave writes a new file and unlinks the old one; a composer that did not
know its own path would leave the original behind and one message would
become two. ComposeContext::draftPath carries it into m_draftPath, which the
autosave already knew how to replace.
MimeParser had no bcc, and nothing had ever needed one. MessageBuilder
writes Bcc into the draft file deliberately and explains why, so a resumed
draft that ignored it would drop every blind recipient from the message the
user then finishes and sends, reporting nothing.
edit_draft is gated on the file being inside a configured drafts folder,
matched on the PATH. A `draft` tag is not enough: notmuch surfaces the
Maildir D flag as one, and a message flagged by another client sits in the
inbox. Offered on ordinary mail, the composer would own a file it did not
write and the first autosave would delete a received message.
And a live defect found on the way, which is most of why this took as long
as it did. updateComposeActions() ran only from onSelectionChanged. Both
signals fire for an ordinary click, so nothing had noticed; but running a
query and setting the current index emits currentRowChanged ALONE, so the
enablement was computed against the previously selected row. Edit draft
stayed disabled on a draft selected that way, and the reply family had the
same blind spot with no test that could see it. Now connected to both.
Reading currentRowChanged is safe here for the reason CLAUDE.md gives: it
answers "which row is current", and no count is read.
WorkerBackedWindow::AccountSpec gains a drafts field, which the two new
tests need and which no fixture could express before.
Diffstat (limited to 'src/types.h')
| -rw-r--r-- | src/types.h | 17 |
1 files changed, 16 insertions, 1 deletions
diff --git a/src/types.h b/src/types.h index 99c271d..7464586 100644 --- a/src/types.h +++ b/src/types.h @@ -247,7 +247,7 @@ struct DatabaseStats /// recipients. This is the same rule Restore already follows. struct ComposeContext { - enum class Kind { New, Reply, ReplyAll, Forward }; + enum class Kind { New, Reply, ReplyAll, Forward, Draft }; QString accountKey; ///< Which account sends. Plain data here; the resolution rules live with whatever builds this context. Kind kind = Kind::New; @@ -256,8 +256,23 @@ struct ComposeContext QStringList references; ///< The original's References plus its Message-ID. QStringList to; ///< Pre-filled, the user's own addresses already stripped. QStringList cc; + QStringList bcc; ///< Only a resumed draft has one; see MimeParser::bcc. QString subject; ///< Re:/Fwd: prefixed, an existing prefix not doubled. QString quotedBody; ///< The >-prefixed original. Empty when the action does not quote. + + /// The body as the user last left it, for a resumed draft ONLY. + /// + /// Separate from quotedBody because it is not a quote and must not be + /// framed like one: no attribution, no blank lines added, no cursor moved + /// to make room. It is the message itself. + QString body; + + /// The draft file this composer OWNS, empty for every other kind. + /// + /// Seeded into ComposeWindow::m_draftPath so the next autosave REPLACES + /// the file rather than leaving the original beside it. Without it a + /// resumed draft becomes two drafts on the first autosave. + QString draftPath; bool seedHtml = false; ///< Did the original carry a text/html part. QStringList attachments; ///< Carried forward for Forward, empty otherwise. }; |
