From 18ff1f33f64428ab96ca0b5ea22e681be564c886 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sun, 13 Sep 2026 20:03:17 +0200 Subject: feat(config): a threaded, path-based spam filter --- src/config.cpp | 21 ++++++++++++++++++++- src/mainwindow.cpp | 1 + 2 files changed, 21 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/config.cpp b/src/config.cpp index 0fa3c85..390848a 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -64,7 +64,8 @@ const QStringList kQueryGenerators = { QStringLiteral("unread"), QStringLiteral("flagged"), QStringLiteral("sent"), QStringLiteral("drafts"), - QStringLiteral("trash") }; + QStringLiteral("trash"), + QStringLiteral("spam") }; /// The tag a generator matches, for the three filters that are a plain tag /// query. Empty for "sent", "drafts" and "trash", which compose from each @@ -1018,6 +1019,8 @@ QString Config::resolvedQuery(const SavedQuery &query) const return allSentQuery(); if (query.generated == QStringLiteral("trash")) return allTrashQuery(); + if (query.generated == QStringLiteral("spam")) + return allSpamQuery(); // An unknown generator was reported on load. Empty rather than the // bare stored query, which for a generated entry is empty anyway and // would otherwise run as "match everything". @@ -1106,6 +1109,10 @@ SavedQuery Config::builtinFilter(const QString &generator) // NOT flat, unlike Sent. A deleted message still belongs to its // conversation, and folding it back is what Sent had to avoid rather // than something every folder filter wants. + } else if (generator == QStringLiteral("spam")) { + filter.name = tr("Spam"); + // NOT flat, like trash: a spam message still belongs to its + // conversation. } return filter; @@ -1138,6 +1145,10 @@ QString Config::resolvedQuery(const SavedQuery &query, const QString all = allTrashQuery(); return all.isEmpty() ? matchNothingQuery() : all; } + if (query.generated == QStringLiteral("spam")) { + const QString all = allSpamQuery(); + return all.isEmpty() ? matchNothingQuery() : all; + } return QStringLiteral("tag:%1").arg(generatorTag(query.generated)); } @@ -1173,6 +1184,14 @@ QString Config::resolvedQuery(const SavedQuery &query, return trash.isEmpty() ? matchNothingQuery() : trash; } + if (query.generated == QStringLiteral("spam")) { + // The account's OWN spam query, for the reason spelled out above the + // sent case: wrapping the all-accounts query in this account's path + // works by accident of path: being hierarchical. + const QString spam = scope.spamQuery(); + return spam.isEmpty() ? matchNothingQuery() : spam; + } + // A tag filter carries no path of its own, so scoping is exactly what // scopedQuery() does. Its parentheses are load-bearing: `path:... and a or // b` binds as `(path:... and a) or b`. diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e7cbf33..2cba6b1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3119,6 +3119,7 @@ void MainWindow::buildSavedQueryRow(QWidget *parent, QVBoxLayout *layout) { QStringLiteral("sent"), QStringLiteral("mail-folder-sent") }, { QStringLiteral("drafts"), QStringLiteral("document-edit") }, { QStringLiteral("trash"), QStringLiteral("user-trash") }, + { QStringLiteral("spam"), QStringLiteral("mail-mark-junk") }, }; button->setIcon( QIcon::fromTheme(filterIcons.value(filter.generated))); -- cgit v1.2.3