diff --git a/src/app/app.component.html b/src/app/app.component.html index ec21e8698..adc6fbdbc 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -347,6 +347,17 @@

No Message Selected

Inline previews + + + + Recipient column + + column.cacheKey === 'subject'); + const defaultSelectedColumn = subjectColumnIndex > -1 ? subjectColumnIndex : 3; + this.canvastable.rowWrapModeWrapColumn = defaultSelectedColumn; + this.canvastable.rowWrapModeDefaultSelectedColumn = defaultSelectedColumn; this.autoAdjustColumnWidths(); } diff --git a/src/app/common/messagelist.spec.ts b/src/app/common/messagelist.spec.ts new file mode 100644 index 000000000..9b0c8d2f8 --- /dev/null +++ b/src/app/common/messagelist.spec.ts @@ -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 . +// ---------- 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'); + }); +}); diff --git a/src/app/common/messagelist.ts b/src/app/common/messagelist.ts index dd7e50249..3f7e1e02d 100644 --- a/src/app/common/messagelist.ts +++ b/src/app/common/messagelist.ts @@ -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'; @@ -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) { this.rows = this._rows; @@ -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; } } diff --git a/src/app/xapian/searchmessagedisplay.spec.ts b/src/app/xapian/searchmessagedisplay.spec.ts new file mode 100644 index 000000000..cfd5b6bc2 --- /dev/null +++ b/src/app/xapian/searchmessagedisplay.spec.ts @@ -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 . +// ---------- 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'); + }); +}); diff --git a/src/app/xapian/searchmessagedisplay.ts b/src/app/xapian/searchmessagedisplay.ts index 8bd686cb7..021cddc3a 100644 --- a/src/app/xapian/searchmessagedisplay.ts +++ b/src/app/xapian/searchmessagedisplay.ts @@ -54,6 +54,17 @@ export class SearchMessageDisplay extends MessageDisplay { filterBy(options: Map) { } + 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[] { @@ -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;