From 802dcef192c028cf4462336b8beb311796b8ff0f Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Thu, 21 Dec 2023 08:24:42 +0100 Subject: [PATCH] webmail: for messages in designated Sent mailbox, show To/Cc/Bcc in italics, and show all correspondents in collapsed thread showing addressees for Sent messages for issue #104 by mattfbacon, thanks for the report! --- webmail/webmail.js | 60 ++++++++++++++++++++++++++++++++++++++--- webmail/webmail.ts | 66 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 118 insertions(+), 8 deletions(-) diff --git a/webmail/webmail.js b/webmail/webmail.js index af05f61..4fc7b44 100644 --- a/webmail/webmail.js +++ b/webmail/webmail.js @@ -2647,6 +2647,62 @@ const newMsgitemView = (mi, msglistView, otherMailbox, listMailboxes, receivedTi } } } + const correspondentAddrs = (miv) => { + let fromAddrs = miv.messageitem.Envelope.From || []; + let toAddrs = []; + if (listMailboxes().find(mb => mb.ID === miv.messageitem.Message.MailboxID)?.Sent) { + toAddrs = [...(miv.messageitem.Envelope.To || []), ...(miv.messageitem.Envelope.CC || []), ...(miv.messageitem.Envelope.BCC || [])]; + } + return [fromAddrs, toAddrs]; + }; + // Correspondents for a message, possibly a collapsed thread root. + const correspondents = () => { + let fromAddrs = []; + let toAddrs = []; + if (msgitemView.isCollapsedThreadRoot()) { + // Gather both all correspondents in thread. + ; + [msgitemView, ...(msgitemView.descendants())].forEach(miv => { + const [fa, ta] = correspondentAddrs(miv); + fromAddrs = [...fromAddrs, ...fa]; + toAddrs = [...toAddrs, ...ta]; + }); + } + else { + [fromAddrs, toAddrs] = correspondentAddrs(msgitemView); + } + const seen = new Set(); + let fa = []; + let ta = []; + for (const a of fromAddrs) { + const k = a.User + '@' + a.Domain.ASCII; + if (!seen.has(k)) { + seen.add(k); + fa.push(a); + } + } + for (const a of toAddrs) { + const k = a.User + '@' + a.Domain.ASCII; + if (!seen.has(k)) { + seen.add(k); + ta.push(a); + } + } + let title = fa.map(a => formatAddressFull(a)).join(', '); + if (ta.length > 0) { + if (title) { + title += ',\n'; + } + title += 'addressed: ' + ta.map(a => formatAddressFull(a)).join(', '); + } + return [ + attr.title(title), + join([ + ...fa.map(a => formatAddressShort(a)), + ...ta.map(a => dom.span(style({ fontStyle: 'italic' }), formatAddressShort(a))), + ], () => ', '), + ]; + }; // When rerendering, we remember active & focus states. So we don't have to make // the caller also call redraw on MsglistView. const active = msgitemView.root && msgitemView.root.classList.contains('active'); @@ -2695,9 +2751,7 @@ const newMsgitemView = (mi, msglistView, otherMailbox, listMailboxes, receivedTi } msglistView.threadCollapse(msgitemView); msglistView.viewportEnsureMessages(); - }) : [])), dom.div(dom._class('msgitemcell', 'msgitemfrom'), dom.div(style({ display: 'flex', justifyContent: 'space-between' }), dom.div(dom._class('msgitemfromtext', 'silenttitle'), - // todo: for collapsed messages, show all participants in thread? - attr.title((mi.Envelope.From || []).map(a => formatAddressFull(a)).join(', ')), join((mi.Envelope.From || []).map(a => formatAddressShort(a)), () => ', ')), identityHeader), + }) : [])), dom.div(dom._class('msgitemcell', 'msgitemfrom'), dom.div(style({ display: 'flex', justifyContent: 'space-between' }), dom.div(dom._class('msgitemfromtext', 'silenttitle'), correspondents()), identityHeader), // Thread messages are connected by a vertical bar. The first and last message are // only half the height of the item, to indicate start/end, and so it stands out // from any thread above/below. diff --git a/webmail/webmail.ts b/webmail/webmail.ts index de97712..a7e8752 100644 --- a/webmail/webmail.ts +++ b/webmail/webmail.ts @@ -1991,6 +1991,66 @@ const newMsgitemView = (mi: api.MessageItem, msglistView: MsglistView, otherMail } } + const correspondentAddrs = (miv: MsgitemView): [api.MessageAddress[], api.MessageAddress[]] => { + let fromAddrs = miv.messageitem.Envelope.From || [] + let toAddrs: api.MessageAddress[] = [] + if (listMailboxes().find(mb => mb.ID === miv.messageitem.Message.MailboxID)?.Sent) { + toAddrs = [...(miv.messageitem.Envelope.To || []), ...(miv.messageitem.Envelope.CC || []), ...(miv.messageitem.Envelope.BCC || [])] + } + return [fromAddrs, toAddrs] + } + + // Correspondents for a message, possibly a collapsed thread root. + const correspondents = () => { + let fromAddrs: api.MessageAddress[] = [] + let toAddrs: api.MessageAddress[] = [] + if (msgitemView.isCollapsedThreadRoot()) { + // Gather both all correspondents in thread. + ;[msgitemView, ...(msgitemView.descendants())].forEach(miv => { + const [fa, ta] = correspondentAddrs(miv) + fromAddrs = [...fromAddrs, ...fa] + toAddrs = [...toAddrs, ...ta] + }) + } else { + [fromAddrs, toAddrs] = correspondentAddrs(msgitemView) + } + + const seen = new Set() + let fa: api.MessageAddress[] = [] + let ta: api.MessageAddress[] = [] + for (const a of fromAddrs) { + const k = a.User+'@'+a.Domain.ASCII + if (!seen.has(k)) { + seen.add(k) + fa.push(a) + } + } + for (const a of toAddrs) { + const k = a.User+'@'+a.Domain.ASCII + if (!seen.has(k)) { + seen.add(k) + ta.push(a) + } + } + let title = fa.map(a => formatAddressFull(a)).join(', ') + if (ta.length > 0) { + if (title) { + title += ',\n' + } + title += 'addressed: '+ta.map(a => formatAddressFull(a)).join(', ') + } + return [ + attr.title(title), + join( + [ + ...fa.map(a => formatAddressShort(a)), + ...ta.map(a => dom.span(style({fontStyle: 'italic'}), formatAddressShort(a))), + ], + () => ', ' + ), + ] + } + // When rerendering, we remember active & focus states. So we don't have to make // the caller also call redraw on MsglistView. const active = msgitemView.root && msgitemView.root.classList.contains('active') @@ -2053,11 +2113,7 @@ const newMsgitemView = (mi: api.MessageItem, msglistView: MsglistView, otherMail ), dom.div(dom._class('msgitemcell', 'msgitemfrom'), dom.div(style({display: 'flex', justifyContent: 'space-between'}), - dom.div(dom._class('msgitemfromtext', 'silenttitle'), - // todo: for collapsed messages, show all participants in thread? - attr.title((mi.Envelope.From || []).map(a => formatAddressFull(a)).join(', ')), - join((mi.Envelope.From || []).map(a => formatAddressShort(a)), () => ', ') - ), + dom.div(dom._class('msgitemfromtext', 'silenttitle'), correspondents()), identityHeader, ), // Thread messages are connected by a vertical bar. The first and last message are