From d37768281c9ce79c0e6e0747d2b8e28c1e5b9033 Mon Sep 17 00:00:00 2001 From: kei Date: Thu, 11 Jun 2026 05:35:34 +0200 Subject: [PATCH] fix(mail): open next message after delete --- src/app/app.component.ts | 23 ++++++++++++++----- src/app/canvastable/canvastable.spec.ts | 26 ++++++++++++++++++++++ src/app/common/messagedisplay.ts | 28 ++++++++++++++++++++++++ src/app/mailviewer/rmm7messageactions.ts | 6 +++++ 4 files changed, 78 insertions(+), 5 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 055e4c6ae..7f03e4f6d 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -232,6 +232,7 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis this.messageActionsHandler.searchService = searchService; this.messageActionsHandler.messageListService = messagelistservice; this.messageActionsHandler.snackBar = snackBar; + this.messageActionsHandler.deleteMessageHandler = (messageIds: number[]) => this.deleteMessages(messageIds); this.renderer.listen(window, 'keydown', (evt: KeyboardEvent) => { if (this.singlemailviewer.messageId) { @@ -809,14 +810,22 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis // Delete selected messages in current canvastable view // If looking at Trash, this will be "delete permanently" - public deleteMessages() { - const messageIds = this.canvastable.rows.selectedMessageIds(); + public deleteMessages(messageIds = this.canvastable.rows.selectedMessageIds()) { + if (messageIds.length === 0) { + return; + } + + const currentMessageId = this.singlemailviewer?.messageId; + const deletingOpenMessage = !!currentMessageId && messageIds.includes(currentMessageId); + const nextMessageId = deletingOpenMessage + ? this.canvastable.rows.getMessageIdAfterRemoving(messageIds, currentMessageId) + : null; this.messageActionsHandler.updateMessages({ messageIds: messageIds, updateLocal: (msgIds: number[]) => { // remove from message display - this.canvastable.rows.removeMessages(messageIds); + this.canvastable.rows.removeMessages(msgIds); this.searchService.deleteMessages(msgIds); if (this.selectedFolder === this.messagelistservice.trashFolderName) { this.messagelistservice.deleteTrashMessages(msgIds); @@ -824,8 +833,12 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis this.messagelistservice.moveMessages(msgIds, this.messagelistservice.trashFolderName); } this.clearSelection(); - if (this.singlemailviewer && this.singlemailviewer.messageId && msgIds.includes(this.singlemailviewer.messageId)) { - this.singlemailviewer.close(); + if (deletingOpenMessage) { + if (this.keepMessagePaneOpen && nextMessageId) { + this.selectRowByMessageId(nextMessageId); + } else if (this.singlemailviewer) { + this.singlemailviewer.close(); + } } }, updateRemote: (msgIds: number[]) => this.rmmapi.deleteMessages(msgIds) diff --git a/src/app/canvastable/canvastable.spec.ts b/src/app/canvastable/canvastable.spec.ts index ef51405f1..5d33bc176 100644 --- a/src/app/canvastable/canvastable.spec.ts +++ b/src/app/canvastable/canvastable.spec.ts @@ -73,4 +73,30 @@ describe('canvastable', () => { expect(fixture.componentInstance.canvastable.floatingTooltip).toBeTruthy(); expect(fixture.componentInstance.canvastable.columnOverlay).toBeTruthy(); }); + + describe('message delete navigation', () => { + it('returns the next visible message after the opened message is removed', () => { + const rows = new MessageList([{ id: 10 }, { id: 20 }, { id: 30 }]); + + expect(rows.getMessageIdAfterRemoving([20], 20)).toBe(30); + }); + + it('skips other removed messages when finding the next message', () => { + const rows = new MessageList([{ id: 10 }, { id: 20 }, { id: 30 }, { id: 40 }]); + + expect(rows.getMessageIdAfterRemoving([20, 30], 20)).toBe(40); + }); + + it('falls back to the previous visible message at the end of the list', () => { + const rows = new MessageList([{ id: 10 }, { id: 20 }, { id: 30 }]); + + expect(rows.getMessageIdAfterRemoving([30], 30)).toBe(20); + }); + + it('returns null when no visible messages remain', () => { + const rows = new MessageList([{ id: 10 }, { id: 20 }]); + + expect(rows.getMessageIdAfterRemoving([10, 20], 10)).toBeNull(); + }); + }); }); diff --git a/src/app/common/messagedisplay.ts b/src/app/common/messagedisplay.ts index 57b7947f6..50a7e7bee 100644 --- a/src/app/common/messagedisplay.ts +++ b/src/app/common/messagedisplay.ts @@ -95,6 +95,34 @@ export abstract class MessageDisplay { }); } + public getMessageIdAfterRemoving(messageIds: number[], currentMessageId: number): number { + if (!currentMessageId) { + return null; + } + + const currentRowIndex = this.findRowByMessageId(currentMessageId); + if (currentRowIndex < 0) { + return null; + } + + const removedMessageIds = new Set(messageIds); + for (let index = currentRowIndex + 1; index < this.rowCount(); index++) { + const messageId = this.getRowMessageId(index); + if (!removedMessageIds.has(messageId)) { + return messageId; + } + } + + for (let index = currentRowIndex - 1; index >= 0; index--) { + const messageId = this.getRowMessageId(index); + if (!removedMessageIds.has(messageId)) { + return messageId; + } + } + + return null; + } + public removeMessages(messageIds: number[]) { const filteredRows = []; this.rows.forEach((value, index) => { diff --git a/src/app/mailviewer/rmm7messageactions.ts b/src/app/mailviewer/rmm7messageactions.ts index 1da73102d..749617e81 100644 --- a/src/app/mailviewer/rmm7messageactions.ts +++ b/src/app/mailviewer/rmm7messageactions.ts @@ -38,6 +38,7 @@ export class RMM7MessageActions implements MessageActions { messageListService: MessageListService; draftDeskService: DraftDeskService; rmmapi: RunboxWebmailAPI; + deleteMessageHandler?: (messageIds: number[]) => void; public updateMessages(args: { messageIds: number[], updateLocal: (messageIds: number[]) => void, @@ -94,6 +95,11 @@ export class RMM7MessageActions implements MessageActions { } public deleteMessage() { + if (this.deleteMessageHandler) { + this.deleteMessageHandler([this.mailViewerComponent.messageId]); + return; + } + this.updateMessages({ messageIds: [this.mailViewerComponent.messageId], updateLocal: (msgIds: number[]) => {