From 04fc8a02cb5263492e02b5bae88cb63a84f42148 Mon Sep 17 00:00:00 2001 From: Torben Dannhauer Date: Fri, 17 Jul 2026 08:00:30 +0200 Subject: [PATCH] fix(imp): refresh search list after trash delete When use_trash is enabled, do not paint an optimistic \Deleted flag on delete. Force a search viewport refresh after deletion so moved messages leave the result list. If move-to-trash cannot run (no expunge rights), fall back to mark-as-deleted instead of a silent no-op. --- js/base.js | 9 +++++- lib/Ajax/Application.php | 8 ++++++ lib/Indices.php | 61 +++++++++++++++++++++------------------- 3 files changed, 48 insertions(+), 30 deletions(-) diff --git a/js/base.js b/js/base.js index 1a761bb9d..c54cf2cdc 100644 --- a/js/base.js +++ b/js/base.js @@ -3802,7 +3802,14 @@ var ImpBase = { opts.vs = this._getSelection(opts); if (this._doMsgAction('deleteMessages', opts, {})) { - this.updateFlag(opts.vs, ImpCore.conf.FLAG_DELETED, true); + /* When using a Trash mailbox (nodeleteshow), messages are moved + * or hidden — they must not be painted as IMAP \Deleted. That + * optimistic mark is only correct for classic mark-for-deletion + * mode, and in search views it often sticks even after a successful + * move-to-trash. Rely on the server disappear/viewport refresh. */ + if (!this.viewport.getMetaData('nodeleteshow')) { + this.updateFlag(opts.vs, ImpCore.conf.FLAG_DELETED, true); + } } }, diff --git a/lib/Ajax/Application.php b/lib/Ajax/Application.php index e9241aa5f..86994bbdb 100644 --- a/lib/Ajax/Application.php +++ b/lib/Ajax/Application.php @@ -419,6 +419,14 @@ public function deleteMsgs(IMP_Indices $indices, $changed, $force = false) $changed = ($this->indices->mailbox->getSort()->sortby == Horde_Imap_Client::SORT_THREAD); } + /* Search BUIDs are session-synthetic and the result set is rebuilt + * after mailbox mutations. Always refresh the viewport so moved/ + * deleted messages leave the search list instead of lingering with + * an optimistic \Deleted appearance. */ + if (!$changed && $this->indices->mailbox->search) { + $changed = true; + } + if ($changed) { $this->addTask('viewport', $this->viewPortData(true)); } elseif (($indices instanceof IMP_Indices_Mailbox) diff --git a/lib/Indices.php b/lib/Indices.php index b42ed8545..0bf76c664 100644 --- a/lib/Indices.php +++ b/lib/Indices.php @@ -444,46 +444,49 @@ public function delete(array $opts = []) $imp_imap = $ob->mbox->imp_imap; $ids_ob = $imp_imap->getIdsOb($ob->uids); $processed = false; + $moved_to_trash = false; /* Trash is only valid for IMAP mailboxes. */ if ($use_trash_mbox && ($ob->mbox != $trash) /* TODO(?): Don't use Trash mailbox for remote accounts. */ - && !$ob->mbox->remote_mbox) { - if ($ob->mbox->access_expunge) { - try { - if ($mark_seen) { - $imp_imap->store($ob->mbox, [ - 'add' => [Horde_Imap_Client::FLAG_SEEN], - 'ids' => $ids_ob, - ]); - } - - $imp_imap->copy($ob->mbox, $trash, [ + && !$ob->mbox->remote_mbox + && $ob->mbox->access_expunge) { + try { + if ($mark_seen) { + $imp_imap->store($ob->mbox, [ + 'add' => [Horde_Imap_Client::FLAG_SEEN], 'ids' => $ids_ob, - 'move' => true, ]); - $affected_mailboxes[strval($ob->mbox)] = $ob->mbox; - $affected_mailboxes[strval($trash)] = $trash; - $processed = true; - } catch (IMP_Imap_Exception $e) { - if ($e->getCode() == $e::OVERQUOTA) { - $notification->push( - _('You are over your quota, so your messages will be permanently deleted instead of moved to the Trash mailbox.'), - 'horde.warning' - ); + } - $idx = new IMP_Indices($ob->mbox, $ob->uids); - return $idx->delete([ - 'keeplog' => !empty($opts['keeplog']), - 'nuke' => true, - ]); - } + $imp_imap->copy($ob->mbox, $trash, [ + 'ids' => $ids_ob, + 'move' => true, + ]); + $affected_mailboxes[strval($ob->mbox)] = $ob->mbox; + $affected_mailboxes[strval($trash)] = $trash; + $processed = true; + $moved_to_trash = true; + } catch (IMP_Imap_Exception $e) { + if ($e->getCode() == $e::OVERQUOTA) { + $notification->push( + _('You are over your quota, so your messages will be permanently deleted instead of moved to the Trash mailbox.'), + 'horde.warning' + ); - return false; + $idx = new IMP_Indices($ob->mbox, $ob->uids); + return $idx->delete([ + 'keeplog' => !empty($opts['keeplog']), + 'nuke' => true, + ]); } + + return false; } - } else { + } + + if (!$moved_to_trash) { /* Delete message logs now. This may result in loss of message * log data for messages that might not be deleted - i.e. if * an error occurs. But 1) the user has already indicated they