From 98ea0ee4691e60b430bd78eb615838da90bce244 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Mon, 24 Aug 2026 12:15:09 +0200 Subject: feat: add a Drafts filter, and close the composer with Ctrl+W Items 138 and 148. The query row carried Unread, Inbox, Important, Sent and Trash, and no Drafts, though the composer has been autosaving into each account's drafts folder since compose shipped. Reaching them meant typing a query by hand. Smaller than its size suggested: Account::draftsQuery() and Config::allDraftsQuery() already existed for the placeholder pane's drafts count, and builtinFilters() derives the row from kQueryGenerators, so the work was the generator entry, two resolvedQuery branches, a label and an icon. It follows TRASH rather than Sent. Folder-matched like both, because `draft` is a Maildir flag notmuch surfaces as a tag while the folder is what the user means and what the composer actually writes into. But NOT flat: Sent is flat so a thread cannot fold the user's own message back into the conversation it answers, and a draft reply belongs with its conversation for the same reason a trashed message does. An account with no drafts folder shows no button, per item 103's rule. The existing row test surfaced that by failing until its fixture configured one, which is the rule working rather than a defect. Ctrl+W closes the composer, which bound nothing at all: the only way out was the title bar. The action is parented to the composer, so it is a WindowShortcut dispatched to the active one only and the main window's namespace is untouched, exactly like the formatting shortcuts. It calls close() rather than doing anything of its own, since closeEvent() already decides whether the draft is saved and a second route out that skipped it would lose the message. The Italian gains "Bozze"; lrelease reports 478 finished, 0 unfinished. --- src/config.cpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) (limited to 'src/config.cpp') diff --git a/src/config.cpp b/src/config.cpp index b148102..cb6168f 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -63,11 +63,12 @@ const QStringList kQueryGenerators = { QStringLiteral("unread"), QStringLiteral("inbox"), QStringLiteral("flagged"), QStringLiteral("sent"), + QStringLiteral("drafts"), QStringLiteral("trash") }; /// The tag a generator matches, for the three filters that are a plain tag -/// query. Empty for "sent", which composes from each account's folder instead -/// and is handled separately. +/// query. Empty for "sent", "drafts" and "trash", which compose from each +/// account's folder instead and are handled separately. QString generatorTag(const QString &generator) { if (generator == QStringLiteral("unread")) @@ -986,6 +987,12 @@ SavedQuery Config::builtinFilter(const QString &generator) // thread would fold the user's sent message back into the conversation // it belongs to, which is item 63's finding. filter.flat = true; + } else if (generator == QStringLiteral("drafts")) { + // The LABEL is translated; the generator stays `drafts`, which is what + // queries.json stores and what a closed set is matched against. + filter.name = tr("Drafts"); + // NOT flat, like Trash and unlike Sent: a draft reply belongs with the + // conversation it answers. } else if (generator == QStringLiteral("trash")) { filter.name = tr("Trash"); // NOT flat, unlike Sent. A deleted message still belongs to its @@ -1015,6 +1022,10 @@ QString Config::resolvedQuery(const SavedQuery &query, const QString all = allSentQuery(); return all.isEmpty() ? matchNothingQuery() : all; } + if (query.generated == QStringLiteral("drafts")) { + const QString all = allDraftsQuery(); + return all.isEmpty() ? matchNothingQuery() : all; + } if (query.generated == QStringLiteral("trash")) { const QString all = allTrashQuery(); return all.isEmpty() ? matchNothingQuery() : all; @@ -1039,6 +1050,13 @@ QString Config::resolvedQuery(const SavedQuery &query, return sent.isEmpty() ? matchNothingQuery() : sent; } + if (query.generated == QStringLiteral("drafts")) { + // The account's OWN drafts query, for the reason spelled out above the + // sent case. + const QString drafts = scope.draftsQuery(); + return drafts.isEmpty() ? matchNothingQuery() : drafts; + } + if (query.generated == QStringLiteral("trash")) { // The account's OWN trash query, for the reason spelled out above the // sent case: wrapping the all-accounts query in this account's path -- cgit v1.2.3