Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/app/mailviewer/messageactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions src/app/mailviewer/rmm6messageactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
109 changes: 109 additions & 0 deletions src/app/mailviewer/rmm7messageactions.spec.ts
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
// ---------- 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<any[]>([
{ 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<any[]>([
{ email: 'me@example.com' },
]),
newDraft,
} as any;
actions.mailViewerComponent = {
messageId: 42,
mailObj: {
mid: 42,
headers: {
'message-id': '<message-42@example.com>',
},
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');
});
});
55 changes: 36 additions & 19 deletions src/app/mailviewer/rmm7messageactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
});
}
Expand All @@ -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,
Expand Down
6 changes: 5 additions & 1 deletion src/app/mailviewer/singlemailviewer.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
matTooltip="Original HTML" [href]="'/rest/v1/email/'+messageId+'/html'" target="_blank">
<mat-icon svgIcon="alert"></mat-icon>
</a>
<button mat-icon-button *ngIf="morebuttonindex < 12" [matMenuTriggerFor]="moreMailActionsMenu" matTooltip="More message actions">
<button mat-icon-button [matMenuTriggerFor]="moreMailActionsMenu" matTooltip="More message actions">
<mat-icon svgIcon="dots-vertical"></mat-icon>
</button>

Expand Down Expand Up @@ -140,6 +140,10 @@
<mat-icon svgIcon="reply"></mat-icon>
<span>Reply</span>
</button>
<button mat-menu-item (click)="messageActionsHandler.replyAndMove(showHTML)">
<mat-icon svgIcon="folder"></mat-icon>
<span>Reply and move</span>
</button>
<button *ngIf="morebuttonindex<4" mat-menu-item (click)="messageActionsHandler.replyToAll(showHTML)">
<mat-icon svgIcon="reply-all"></mat-icon>
<span>Reply to all</span>
Expand Down
3 changes: 3 additions & 0 deletions src/app/mailviewer/singlemailviewer.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.');
}
Expand Down