diff --git a/src/app/mailviewer/messageactions.ts b/src/app/mailviewer/messageactions.ts index a8bdab9dc..6ad846d86 100644 --- a/src/app/mailviewer/messageactions.ts +++ b/src/app/mailviewer/messageactions.ts @@ -27,6 +27,7 @@ export interface MessageActions { flag(); unflag(); reply(useHTML?: boolean); + replyAndMove(useHTML?: boolean); replyToAll(useHTML?: boolean); forward(useHTML?: boolean); markSeen(seen_flag_value?: number); diff --git a/src/app/mailviewer/rmm6messageactions.ts b/src/app/mailviewer/rmm6messageactions.ts index fe6ae798c..bd08924b8 100644 --- a/src/app/mailviewer/rmm6messageactions.ts +++ b/src/app/mailviewer/rmm6messageactions.ts @@ -49,6 +49,10 @@ export class RMM6MessageActions implements MessageActions { this.openCompose('/mail/reply?message=' + this.mailViewerComponent.messageId); } + replyAndMove() { + this.snackBar.open('Not supported in RMM6 yet', null, {duration: 1000}); + } + replyToAll() { this.openCompose('/mail/replyall?message=' + this.mailViewerComponent.messageId); } diff --git a/src/app/mailviewer/rmm7messageactions.spec.ts b/src/app/mailviewer/rmm7messageactions.spec.ts new file mode 100644 index 000000000..e79c13381 --- /dev/null +++ b/src/app/mailviewer/rmm7messageactions.spec.ts @@ -0,0 +1,109 @@ +// --------- BEGIN RUNBOX LICENSE --------- +// Copyright (C) 2016-2026 Runbox Solutions AS (runbox.com). +// +// This file is part of Runbox 7. +// +// Runbox 7 is free software: You can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at your +// option) any later version. +// +// Runbox 7 is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Runbox 7. If not, see . +// ---------- END RUNBOX LICENSE ---------- + +import { BehaviorSubject, of } from 'rxjs'; + +import { MoveMessageDialogComponent } from '../actions/movemessage.action'; +import { DraftFormModel } from '../compose/draftdesk.service'; +import { RMM7MessageActions } from './rmm7messageactions'; + +describe('RMM7MessageActions', () => { + function makeActions() { + const actions = new RMM7MessageActions(); + const moveToFolder = jasmine.createSpy('moveToFolder').and.returnValue(of({ + status: 'success', + result: { changed_time: 0 }, + })); + const close = jasmine.createSpy('close'); + const newDraft = jasmine.createSpy('newDraft'); + const folderListSubject = new BehaviorSubject([ + { folderId: 1, folderPath: 'Inbox' }, + { folderId: 7, folderPath: 'Archive' }, + ]); + + actions.dialog = { + open: jasmine.createSpy('open').and.returnValue({ + afterClosed: () => of(7), + }), + } as any; + actions.draftDeskService = { + fromsSubject: new BehaviorSubject([ + { email: 'me@example.com' }, + ]), + newDraft, + } as any; + actions.mailViewerComponent = { + messageId: 42, + mailObj: { + mid: 42, + headers: { + 'message-id': '', + }, + from: [ + { name: 'Sender', address: 'sender@example.com' }, + ], + to: [ + { name: 'Me', address: 'me@example.com' }, + ], + date: new Date(2026, 0, 2, 9, 30), + subject: 'Source subject', + rawtext: 'Message body', + }, + close, + } as any; + actions.messageListService = { + currentFolder: 'Inbox', + folderListSubject, + moveMessages: jasmine.createSpy('moveMessages'), + rmmapi: { moveToFolder }, + } as any; + actions.rmmapi = { + showBackendErrors: jasmine.createSpy('showBackendErrors'), + } as any; + actions.searchService = { + moveMessagesToFolder: jasmine.createSpy('moveMessagesToFolder'), + indexWorker: { + postMessage: jasmine.createSpy('postMessage'), + }, + } as any; + + return { + actions, + close, + moveToFolder, + newDraft, + }; + } + + it('moves the open message before opening a reply draft', () => { + const { actions, close, moveToFolder, newDraft } = makeActions(); + + (actions as any).replyAndMove(false); + + expect(actions.dialog.open).toHaveBeenCalledWith(MoveMessageDialogComponent); + expect(actions.searchService.moveMessagesToFolder).toHaveBeenCalledWith([42], 'Archive'); + expect(actions.messageListService.moveMessages).toHaveBeenCalledWith([42], 'Archive'); + expect(moveToFolder).toHaveBeenCalledWith([42], 7, 1); + expect(newDraft).toHaveBeenCalled(); + const draft = newDraft.calls.mostRecent().args[0] as DraftFormModel; + expect(draft.reply_to_id as any).toBe(42); + expect(draft.subject).toBe('Re: Source subject'); + expect(close).toHaveBeenCalledWith('goToDraftDesk'); + }); +}); diff --git a/src/app/mailviewer/rmm7messageactions.ts b/src/app/mailviewer/rmm7messageactions.ts index 1da73102d..e6dffdc1f 100644 --- a/src/app/mailviewer/rmm7messageactions.ts +++ b/src/app/mailviewer/rmm7messageactions.ts @@ -65,30 +65,37 @@ export class RMM7MessageActions implements MessageActions { }); } + private moveCurrentMessageToFolder(folder: number, closeAfterMove: boolean, afterwards?: (result) => void) { + this.updateMessages({ + messageIds: [this.mailViewerComponent.messageId], + updateLocal: (msgIds: number[]) => { + let folderPath; + this.messageListService.folderListSubject.pipe(take(1)) + .subscribe((folders) => { + folderPath = folders.find(fld => fld.folderId === folder).folderPath; + }); + console.log('Moving to folder', folderPath, this.mailViewerComponent.messageId); + this.searchService.moveMessagesToFolder(msgIds, folderPath); + this.messageListService.moveMessages(msgIds, folderPath); + if (closeAfterMove) { + this.mailViewerComponent.close(); + } + }, + updateRemote: (msgIds: number[]) => { + const userFolders = this.messageListService.folderListSubject.value; + const currentFolderId = userFolders.find(fld => fld.folderPath === this.messageListService.currentFolder).folderId; + return this.messageListService.rmmapi.moveToFolder(msgIds, folder, currentFolderId); + }, + afterwards, + }); + } + public moveToFolder() { const dialogRef = this.dialog.open(MoveMessageDialogComponent); dialogRef.afterClosed().subscribe(folder => { if (folder) { - this.updateMessages({ - messageIds: [this.mailViewerComponent.messageId], - updateLocal: (msgIds: number[]) => { - let folderPath; - this.messageListService.folderListSubject.pipe(take(1)) - .subscribe((folders) => { - folderPath = folders.find(fld => fld.folderId === folder).folderPath; - }); - console.log('Moving to folder', folderPath, this.mailViewerComponent.messageId); - this.searchService.moveMessagesToFolder(msgIds, folderPath); - this.messageListService.moveMessages(msgIds, folderPath); - this.mailViewerComponent.close(); - }, - updateRemote: (msgIds: number[]) => { - const userFolders = this.messageListService.folderListSubject.value; - const currentFolderId = userFolders.find(fld => fld.folderPath === this.messageListService.currentFolder).folderId; - return this.messageListService.rmmapi.moveToFolder(msgIds, folder, currentFolderId); - } - }); + this.moveCurrentMessageToFolder(folder, true); } }); } @@ -115,6 +122,16 @@ export class RMM7MessageActions implements MessageActions { this.mailViewerComponent.close('goToDraftDesk'); } + public replyAndMove(useHTML: boolean) { + const dialogRef = this.dialog.open(MoveMessageDialogComponent); + + dialogRef.afterClosed().subscribe(folder => { + if (folder) { + this.moveCurrentMessageToFolder(folder, false, () => this.reply(useHTML)); + } + }); + } + public replyToAll(useHTML: boolean) { this.draftDeskService.newDraft(DraftFormModel.reply( this.mailViewerComponent.mailObj, diff --git a/src/app/mailviewer/singlemailviewer.component.html b/src/app/mailviewer/singlemailviewer.component.html index 5eaf9debb..761bd8225 100644 --- a/src/app/mailviewer/singlemailviewer.component.html +++ b/src/app/mailviewer/singlemailviewer.component.html @@ -77,7 +77,7 @@ matTooltip="Original HTML" [href]="'/rest/v1/email/'+messageId+'/html'" target="_blank"> - @@ -140,6 +140,10 @@ Reply +