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">
-
+
+
+ Reply and move
+
Reply to all
diff --git a/src/app/mailviewer/singlemailviewer.component.spec.ts b/src/app/mailviewer/singlemailviewer.component.spec.ts
index d4bc44f93..b46a54356 100644
--- a/src/app/mailviewer/singlemailviewer.component.spec.ts
+++ b/src/app/mailviewer/singlemailviewer.component.spec.ts
@@ -192,6 +192,9 @@ describe('SingleMailViewerComponent', () => {
reply(useHTML?: boolean) {
throw new Error('Method not implemented.');
}
+ replyAndMove(useHTML?: boolean) {
+ throw new Error('Method not implemented.');
+ }
replyToAll(useHTML?: boolean) {
throw new Error('Method not implemented.');
}