diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-14 13:03:35 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-14 13:03:35 +0200 |
| commit | d714483b6027425923340d1bcfa0263b8e7ac0bc (patch) | |
| tree | b9f0b4851fa5cd00dfda181777f021279e027db6 /src/mainwindow.cpp | |
| parent | 3ba5e6b68a0f4e91884043fab9a705cf7d15b968 (diff) | |
| download | qtmaildir-d714483b6027425923340d1bcfa0263b8e7ac0bc.tar.gz qtmaildir-d714483b6027425923340d1bcfa0263b8e7ac0bc.zip | |
fix: gate spam like delete, and keep one origin in the model
Diffstat (limited to 'src/mainwindow.cpp')
| -rw-r--r-- | src/mainwindow.cpp | 43 |
1 files changed, 42 insertions, 1 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 476ff18..4b116ba 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3931,6 +3931,15 @@ void MainWindow::refreshTrashActions() && !m_replySelectionHidesDelete); } + // Mark spam follows Delete exactly: one reply cannot be moved out of its + // conversation, and the trash view does not afford it. Same two hides, ORed + // rather than fought over, so it reads the same flag instead of walking the + // selection a second time. + if (auto *spam = m_actions.value(QStringLiteral("spam"))) { + spam->setVisible((!haveSelection || !inTrash) + && !m_replySelectionHidesDelete); + } + // The mirror, which shipped beside it: Restore was added unconditionally // to both menus and so was offered on mail that was never deleted. if (auto *restore = m_actions.value(QStringLiteral("restore"))) { @@ -6466,6 +6475,29 @@ QString MainWindow::originTagFor(const QString &dbRelativeFolder) const return QString(kOriginTagPrefix) + accountRelative; } +QStringList MainWindow::originTagsToStrip(const QStringList &messageIds, + const QStringList &added) const +{ + const bool writingOrigin = + std::any_of(added.cbegin(), added.cend(), [](const QString &tag) { + return tag == kOriginTagPlaceholder() + || tag.startsWith(QLatin1String(kOriginTagPrefix)); + }); + if (!writingOrigin) + return {}; + + QStringList stripped; + for (const QString &messageId : messageIds) { + for (const QString &tag : m_model->messageById(messageId).tags) { + if (tag.startsWith(QLatin1String(kOriginTagPrefix)) + && !added.contains(tag) && !stripped.contains(tag)) { + stripped.append(tag); + } + } + } + return stripped; +} + void MainWindow::trashThreads(const QStringList &threadIds) { if (threadIds.isEmpty()) @@ -7331,6 +7363,11 @@ void MainWindow::sendMove(const QStringList &messageIds, if (tag != kOriginTagPlaceholder()) displayRemove.append(tag); } + // The model's half of the worker's overwrite rule. This move is writing a + // new origin, so every `moved-from:` the model still holds is stale and + // goes with it; otherwise the confirmed update below appends the new tag + // beside the old one and restoreSelected() reads whichever comes first. + displayRemove += originTagsToStrip(messageIds, add); // A thread-scoped move already repainted its rows in // trashThreads() / untrashThreads(), synchronously, before // the worker was asked to resolve the threads at all. Repeating it here @@ -7586,7 +7623,11 @@ void MainWindow::onMessagesMoved(const QMap<QString, QString> &originByMessageId }; const QStringList resolvedAdd = resolve(pending.add); - const QStringList resolvedRemove = resolve(pending.remove); + QStringList resolvedRemove = resolve(pending.remove); + // The confirmed half of the same rule, and the one that matters when + // the optimistic update was skipped (a whole-thread move): the new + // origin replaces any other the model still holds. + resolvedRemove += originTagsToStrip(it.value(), resolvedAdd); sendMessageTagChange(it.value(), resolvedAdd, resolvedRemove, pending.description); |
