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
11 changes: 11 additions & 0 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,17 @@ <h3 class="noMessageSelectedNotification">No Message Selected</h3>
Inline previews
</mat-checkbox>
</mat-list-item>
<mat-list-item *ngIf="!mobileQuery.matches">
<mat-checkbox [(ngModel)]="displayRecipientColumn"
matLine
(change)="saveRecipientColumnSetting()"
(click)="$event.stopPropagation()"
class="tableViewOptionsMenuElement"
>
<mat-icon svgIcon="email-open" class="tableViewOptionsMenuElement" matTooltip="Recipient address column"></mat-icon>
Recipient column
</mat-checkbox>
</mat-list-item>
<!-- currently only supporting threading for local index -->
<mat-list-item>
<mat-checkbox [(ngModel)]="conversationGroupingCheckbox"
Expand Down
15 changes: 13 additions & 2 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ const LOCAL_STORAGE_SETTING_MAILVIEWER_ON_RIGHT_SIDE_IF_MOBILE = 'mailViewerOnRi
const LOCAL_STORAGE_SETTING_MAILVIEWER_ON_RIGHT_SIDE = 'mailViewerOnRightSide';
const LOCAL_STORAGE_VIEWMODE = 'rmm7mailViewerViewMode';
const LOCAL_STORAGE_SHOWCONTENTPREVIEW = 'rmm7mailViewerContentPreview';
const LOCAL_STORAGE_SHOW_RECIPIENT_COLUMN = 'rmm7mailViewerShowRecipientColumn';
const LOCAL_STORAGE_KEEP_PANE = 'keepMessagePaneOpen';
const LOCAL_STORAGE_SHOW_UNREAD_ONLY = 'rmm7mailViewerShowUnreadOnly';
const LOCAL_STORAGE_SHOW_POPULAR_RECIPIENTS = 'showPopularRecipients';
Expand Down Expand Up @@ -122,6 +123,7 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis
showingSearchResults = false; // Toggle if showing from message list or xapian search
showingWebSocketSearchResults = false;
displayFolderColumn = false;
displayRecipientColumn = false;

mailViewerOnRightSide = true;
mailViewerRightSideWidth = '40%';
Expand Down Expand Up @@ -301,6 +303,7 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis
this.canvastable.showContentTextPreview = prefs.get(`${this.preferenceService.prefGroup}:${LOCAL_STORAGE_SHOWCONTENTPREVIEW}`) === 'true';
this.canvastable.columnWidths = prefs.get(`${this.preferenceService.prefGroup}:canvasNamedColumnWidthsBySet`) || {};
}
this.displayRecipientColumn = prefs.get(`${this.preferenceService.prefGroup}:${LOCAL_STORAGE_SHOW_RECIPIENT_COLUMN}`) === 'true';
this.keepMessagePaneOpen = prefs.get(`${this.preferenceService.prefGroup}:${LOCAL_STORAGE_KEEP_PANE}`) === 'true';
this.unreadMessagesOnlyCheckbox = prefs.get(`${DefaultPrefGroups.Global}:${LOCAL_STORAGE_SHOW_UNREAD_ONLY}`) === 'true';
this.viewmode = prefs.get(`${this.preferenceService.prefGroup}:${LOCAL_STORAGE_VIEWMODE}`);
Expand Down Expand Up @@ -678,6 +681,12 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis
// localStorage.setItem(LOCAL_STORAGE_SHOWCONTENTPREVIEW, setting);
}

saveRecipientColumnSetting(): void {
const setting = this.displayRecipientColumn ? 'true' : 'false';
this.preferenceService.set(this.preferenceService.prefGroup, LOCAL_STORAGE_SHOW_RECIPIENT_COLUMN, setting);
this.resetColumns();
}

public trainSpam(params) {
const msg = params.is_spam ? 'Reporting spam' : 'Reporting not spam';
this.snackBar.open( msg, 'Dismiss' );
Expand Down Expand Up @@ -1232,8 +1241,10 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis
if (this.canvastable && this.canvastable.rows) {
this.canvastable.columns = this.canvastable.rows.getCanvasTableColumns(this);
}
this.canvastable.rowWrapModeWrapColumn = 3;
this.canvastable.rowWrapModeDefaultSelectedColumn = 3;
const subjectColumnIndex = this.canvastable.columns.findIndex((column) => column.cacheKey === 'subject');
const defaultSelectedColumn = subjectColumnIndex > -1 ? subjectColumnIndex : 3;
this.canvastable.rowWrapModeWrapColumn = defaultSelectedColumn;
this.canvastable.rowWrapModeDefaultSelectedColumn = defaultSelectedColumn;
this.autoAdjustColumnWidths();
}

Expand Down
73 changes: 73 additions & 0 deletions src/app/common/messagelist.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// --------- BEGIN RUNBOX LICENSE ---------
// Copyright (C) 2016-2022 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 { MailAddressInfo } from './mailaddressinfo';
import { MessageList } from './messagelist';

describe('MessageList', () => {
const makeMessage = (to: MailAddressInfo[], cc: MailAddressInfo[] = []) => ({
id: 1,
messageDate: new Date('2026-01-01T12:00:00Z'),
from: [new MailAddressInfo('Sender', 'sender@example.com')],
to,
cc,
subject: 'Subject',
plaintext: '',
size: 100,
attachment: false,
answeredFlag: false,
flaggedFlag: false
});

it('adds a recipient address column when enabled for incoming folders', () => {
const rows = new MessageList([
makeMessage([
new MailAddressInfo('Alias', 'alias+shopping@runbox.com')
], [
new MailAddressInfo('Copy', 'copy@example.com')
])
]);

const columns = rows.getCanvasTableColumns({
selectedFolder: 'Inbox',
displayRecipientColumn: true
});
const recipientColumn = columns.find((column) => column.cacheKey === 'recipient');

expect(recipientColumn).toBeTruthy();
expect(columns.findIndex((column) => column.cacheKey === 'recipient')).toBeLessThan(
columns.findIndex((column) => column.cacheKey === 'subject')
);
expect(recipientColumn.getValue(0)).toBe('alias+shopping@runbox.com, copy@example.com');
});

it('does not add a duplicate recipient column for Sent folders', () => {
const rows = new MessageList([
makeMessage([new MailAddressInfo('Recipient', 'recipient@example.com')])
]);

const columns = rows.getCanvasTableColumns({
selectedFolder: 'Sent',
displayRecipientColumn: true
});

expect(columns.find((column) => column.cacheKey === 'recipient')).toBeUndefined();
expect(columns.find((column) => column.cacheKey === 'from').name).toBe('To');
});
});
29 changes: 29 additions & 0 deletions src/app/common/messagelist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import { MessageDisplay } from '../common/messagedisplay';
import { MessageInfo } from './messageinfo';
import { MailAddressInfo } from './mailaddressinfo';
import { MessageTableRowTool} from '../messagetable/messagetablerow';
import { CanvasTableColumn } from '../canvastable/canvastablecolumn';

Expand Down Expand Up @@ -62,6 +63,22 @@ export class MessageList extends MessageDisplay {
'';
}

getRecipientColumnValueForRow(rowIndex: number): string {
const rowobj = this.rows[rowIndex];
return this.formatRecipientAddresses([].concat(rowobj.to || [], rowobj.cc || []));
}

private formatRecipientAddresses(recipients: MailAddressInfo[]): string {
return recipients
.map((mailAddr) => mailAddr.address || mailAddr.nameAndAddress || '')
.filter((address) => address.length > 0)
.join(', ');
}

private shouldShowRecipientColumn(app: { displayRecipientColumn: boolean; selectedFolder: string }): boolean {
return app.displayRecipientColumn && app.selectedFolder.indexOf('Sent') !== 0;
}

// filter visible rows by whatever options the frontend has
filterBy(options: Map<string, any>) {
this.rows = this._rows;
Expand Down Expand Up @@ -155,6 +172,18 @@ export class MessageList extends MessageDisplay {
}
];

if (this.shouldShowRecipientColumn(app)) {
columns.splice(3, 0, {
name: 'Recipient',
cacheKey: 'recipient',
sortColumn: null,
rowWrapModeHidden: true,
getValue: (rowIndex: number): string => this.getRecipientColumnValueForRow(rowIndex),
draggable: true,
width: 220
});
}

return columns;
}
}
62 changes: 62 additions & 0 deletions src/app/xapian/searchmessagedisplay.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// --------- BEGIN RUNBOX LICENSE ---------
// Copyright (C) 2016-2022 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 { SearchMessageDisplay } from './searchmessagedisplay';

describe('SearchMessageDisplay', () => {
const makeDisplay = () => new SearchMessageDisplay({
getDocData: () => ({
from: 'Sender',
subject: 'Subject',
recipients: ['alias@runbox.com', 'alias+tag@runbox.com'],
textcontent: '',
attachment: false,
answered: false,
flagged: false
})
}, [[42]]);

it('adds a recipient address column for indexed incoming results', () => {
const columns = makeDisplay().getCanvasTableColumns({
selectedFolder: 'Inbox',
displayFolderColumn: false,
displayRecipientColumn: true,
viewmode: 'messages'
});
const recipientColumn = columns.find((column) => column.cacheKey === 'recipient');

expect(recipientColumn).toBeTruthy();
expect(columns.findIndex((column) => column.cacheKey === 'recipient')).toBeLessThan(
columns.findIndex((column) => column.cacheKey === 'subject')
);
expect(recipientColumn.getValue(0)).toBe('alias@runbox.com, alias+tag@runbox.com');
});

it('keeps Sent folder results on the existing To column when not showing all folders', () => {
const columns = makeDisplay().getCanvasTableColumns({
selectedFolder: 'Sent',
displayFolderColumn: false,
displayRecipientColumn: true,
viewmode: 'messages'
});

expect(columns.find((column) => column.cacheKey === 'recipient')).toBeUndefined();
expect(columns.find((column) => column.cacheKey === 'from').name).toBe('To');
});
});
23 changes: 23 additions & 0 deletions src/app/xapian/searchmessagedisplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ export class SearchMessageDisplay extends MessageDisplay {
filterBy(options: Map<string, any>) {
}

getRecipientColumnValueForRow(rowIndex: number): string {
return this.searchService.getDocData(this.getRowId(rowIndex)).recipients.join(', ');
}

private shouldShowRecipientColumn(
app: { displayRecipientColumn: boolean; displayFolderColumn: boolean; selectedFolder: string }
): boolean {
return app.displayRecipientColumn
&& (app.displayFolderColumn || app.selectedFolder.indexOf('Sent') !== 0);
}

// columns
// app is a Component (currently)
public getCanvasTableColumns(app: any): CanvasTableColumn[] {
Expand Down Expand Up @@ -107,6 +118,18 @@ export class SearchMessageDisplay extends MessageDisplay {
}
];

if (this.shouldShowRecipientColumn(app)) {
columns.splice(3, 0, {
name: 'Recipient',
draggable: true,
cacheKey: 'recipient',
sortColumn: null,
rowWrapModeHidden: true,
getValue: (rowIndex): string => this.getRecipientColumnValueForRow(rowIndex),
width: 220
});
}

if (app.viewmode === 'conversations') {
// Array containing row (conversation) objects waiting to be counted
let currentCountObject = null;
Expand Down