diff --git a/webmail/lib.ts b/webmail/lib.ts index 687be7c..b623d22 100644 --- a/webmail/lib.ts +++ b/webmail/lib.ts @@ -327,7 +327,7 @@ const equalAddress = (a: api.MessageAddress, b: api.MessageAddress) => { // loadMsgheaderView loads the common message headers into msgheaderelem. // if refineKeyword is set, labels are shown and a click causes a call to // refineKeyword. -const loadMsgheaderView = (msgheaderelem: HTMLElement, mi: api.MessageItem, refineKeyword: null | ((kw: string) => Promise)) => { +const loadMsgheaderView = (msgheaderelem: HTMLElement, mi: api.MessageItem, moreHeaders: string[], refineKeyword: null | ((kw: string) => Promise)) => { const msgenv = mi.Envelope const received = mi.Message.Received const receivedlocal = new Date(received.getTime() - received.getTimezoneOffset()*60*1000) @@ -379,5 +379,11 @@ const loadMsgheaderView = (msgheaderelem: HTMLElement, mi: api.MessageItem, refi ) ), ), + moreHeaders.map(k => + dom.tr( + dom.td(k+':', style({textAlign: 'right', color: '#555', whiteSpace: 'nowrap'})), + dom.td(), + ) + ), ) } diff --git a/webmail/msg.js b/webmail/msg.js index e908e87..d072364 100644 --- a/webmail/msg.js +++ b/webmail/msg.js @@ -912,7 +912,7 @@ const equalAddress = (a, b) => { // loadMsgheaderView loads the common message headers into msgheaderelem. // if refineKeyword is set, labels are shown and a click causes a call to // refineKeyword. -const loadMsgheaderView = (msgheaderelem, mi, refineKeyword) => { +const loadMsgheaderView = (msgheaderelem, mi, moreHeaders, refineKeyword) => { const msgenv = mi.Envelope; const received = mi.Message.Received; const receivedlocal = new Date(received.getTime() - received.getTimezoneOffset() * 60 * 1000); @@ -920,7 +920,7 @@ const loadMsgheaderView = (msgheaderelem, mi, refineKeyword) => { // todo: make addresses clickable, start search (keep current mailbox if any) dom.tr(dom.td('From:', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td(style({ width: '100%' }), dom.div(style({ display: 'flex', justifyContent: 'space-between' }), dom.div(join((msgenv.From || []).map(a => formatAddressFull(a)), () => ', ')), dom.div(attr.title('Received: ' + received.toString() + ';\nDate header in message: ' + (msgenv.Date ? msgenv.Date.toString() : '(missing/invalid)')), receivedlocal.toDateString() + ' ' + receivedlocal.toTimeString().split(' ')[0])))), (msgenv.ReplyTo || []).length === 0 ? [] : dom.tr(dom.td('Reply-To:', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td(join((msgenv.ReplyTo || []).map(a => formatAddressFull(a)), () => ', '))), dom.tr(dom.td('To:', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td(join((msgenv.To || []).map(a => formatAddressFull(a)), () => ', '))), (msgenv.CC || []).length === 0 ? [] : dom.tr(dom.td('Cc:', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td(join((msgenv.CC || []).map(a => formatAddressFull(a)), () => ', '))), (msgenv.BCC || []).length === 0 ? [] : dom.tr(dom.td('Bcc:', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td(join((msgenv.BCC || []).map(a => formatAddressFull(a)), () => ', '))), dom.tr(dom.td('Subject:', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td(dom.div(style({ display: 'flex', justifyContent: 'space-between' }), dom.div(msgenv.Subject || ''), dom.div(mi.IsSigned ? dom.span(style({ backgroundColor: '#666', padding: '0px 0.15em', fontSize: '.9em', color: 'white', borderRadius: '.15em' }), 'Message has a signature') : [], mi.IsEncrypted ? dom.span(style({ backgroundColor: '#666', padding: '0px 0.15em', fontSize: '.9em', color: 'white', borderRadius: '.15em' }), 'Message is encrypted') : [], refineKeyword ? (mi.Message.Keywords || []).map(kw => dom.clickbutton(dom._class('keyword'), kw, async function click() { await refineKeyword(kw); - })) : []))))); + })) : [])))), moreHeaders.map(k => dom.tr(dom.td(k + ':', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td()))); }; // Javascript is generated from typescript, do not modify generated javascript because changes will be overwritten. const init = () => { @@ -930,7 +930,7 @@ const init = () => { dom._kids(msgattachmentview, dom.div(style({ borderTop: '1px solid #ccc' }), dom.div(dom._class('pad'), 'Attachments: ', join(mi.Attachments.map(a => a.Filename || '(unnamed)'), () => ', ')))); } const msgheaderview = dom.table(style({ marginBottom: '1ex', width: '100%' })); - loadMsgheaderView(msgheaderview, mi, null); + loadMsgheaderView(msgheaderview, mi, [], null); const l = window.location.pathname.split('/'); const w = l[l.length - 1]; let iframepath; diff --git a/webmail/msg.ts b/webmail/msg.ts index 9a7deae..8009688 100644 --- a/webmail/msg.ts +++ b/webmail/msg.ts @@ -20,7 +20,7 @@ const init = () => { } const msgheaderview = dom.table(style({marginBottom: '1ex', width: '100%'})) - loadMsgheaderView(msgheaderview, mi, null) + loadMsgheaderView(msgheaderview, mi, [], null) const l = window.location.pathname.split('/') const w = l[l.length-1] diff --git a/webmail/text.js b/webmail/text.js index ce759e5..5a0f78c 100644 --- a/webmail/text.js +++ b/webmail/text.js @@ -912,7 +912,7 @@ const equalAddress = (a, b) => { // loadMsgheaderView loads the common message headers into msgheaderelem. // if refineKeyword is set, labels are shown and a click causes a call to // refineKeyword. -const loadMsgheaderView = (msgheaderelem, mi, refineKeyword) => { +const loadMsgheaderView = (msgheaderelem, mi, moreHeaders, refineKeyword) => { const msgenv = mi.Envelope; const received = mi.Message.Received; const receivedlocal = new Date(received.getTime() - received.getTimezoneOffset() * 60 * 1000); @@ -920,7 +920,7 @@ const loadMsgheaderView = (msgheaderelem, mi, refineKeyword) => { // todo: make addresses clickable, start search (keep current mailbox if any) dom.tr(dom.td('From:', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td(style({ width: '100%' }), dom.div(style({ display: 'flex', justifyContent: 'space-between' }), dom.div(join((msgenv.From || []).map(a => formatAddressFull(a)), () => ', ')), dom.div(attr.title('Received: ' + received.toString() + ';\nDate header in message: ' + (msgenv.Date ? msgenv.Date.toString() : '(missing/invalid)')), receivedlocal.toDateString() + ' ' + receivedlocal.toTimeString().split(' ')[0])))), (msgenv.ReplyTo || []).length === 0 ? [] : dom.tr(dom.td('Reply-To:', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td(join((msgenv.ReplyTo || []).map(a => formatAddressFull(a)), () => ', '))), dom.tr(dom.td('To:', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td(join((msgenv.To || []).map(a => formatAddressFull(a)), () => ', '))), (msgenv.CC || []).length === 0 ? [] : dom.tr(dom.td('Cc:', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td(join((msgenv.CC || []).map(a => formatAddressFull(a)), () => ', '))), (msgenv.BCC || []).length === 0 ? [] : dom.tr(dom.td('Bcc:', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td(join((msgenv.BCC || []).map(a => formatAddressFull(a)), () => ', '))), dom.tr(dom.td('Subject:', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td(dom.div(style({ display: 'flex', justifyContent: 'space-between' }), dom.div(msgenv.Subject || ''), dom.div(mi.IsSigned ? dom.span(style({ backgroundColor: '#666', padding: '0px 0.15em', fontSize: '.9em', color: 'white', borderRadius: '.15em' }), 'Message has a signature') : [], mi.IsEncrypted ? dom.span(style({ backgroundColor: '#666', padding: '0px 0.15em', fontSize: '.9em', color: 'white', borderRadius: '.15em' }), 'Message is encrypted') : [], refineKeyword ? (mi.Message.Keywords || []).map(kw => dom.clickbutton(dom._class('keyword'), kw, async function click() { await refineKeyword(kw); - })) : []))))); + })) : [])))), moreHeaders.map(k => dom.tr(dom.td(k + ':', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td()))); }; // Javascript is generated from typescript, do not modify generated javascript because changes will be overwritten. const init = async () => { diff --git a/webmail/webmail.js b/webmail/webmail.js index 4842c88..7d0d30a 100644 --- a/webmail/webmail.js +++ b/webmail/webmail.js @@ -912,7 +912,7 @@ const equalAddress = (a, b) => { // loadMsgheaderView loads the common message headers into msgheaderelem. // if refineKeyword is set, labels are shown and a click causes a call to // refineKeyword. -const loadMsgheaderView = (msgheaderelem, mi, refineKeyword) => { +const loadMsgheaderView = (msgheaderelem, mi, moreHeaders, refineKeyword) => { const msgenv = mi.Envelope; const received = mi.Message.Received; const receivedlocal = new Date(received.getTime() - received.getTimezoneOffset() * 60 * 1000); @@ -920,7 +920,7 @@ const loadMsgheaderView = (msgheaderelem, mi, refineKeyword) => { // todo: make addresses clickable, start search (keep current mailbox if any) dom.tr(dom.td('From:', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td(style({ width: '100%' }), dom.div(style({ display: 'flex', justifyContent: 'space-between' }), dom.div(join((msgenv.From || []).map(a => formatAddressFull(a)), () => ', ')), dom.div(attr.title('Received: ' + received.toString() + ';\nDate header in message: ' + (msgenv.Date ? msgenv.Date.toString() : '(missing/invalid)')), receivedlocal.toDateString() + ' ' + receivedlocal.toTimeString().split(' ')[0])))), (msgenv.ReplyTo || []).length === 0 ? [] : dom.tr(dom.td('Reply-To:', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td(join((msgenv.ReplyTo || []).map(a => formatAddressFull(a)), () => ', '))), dom.tr(dom.td('To:', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td(join((msgenv.To || []).map(a => formatAddressFull(a)), () => ', '))), (msgenv.CC || []).length === 0 ? [] : dom.tr(dom.td('Cc:', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td(join((msgenv.CC || []).map(a => formatAddressFull(a)), () => ', '))), (msgenv.BCC || []).length === 0 ? [] : dom.tr(dom.td('Bcc:', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td(join((msgenv.BCC || []).map(a => formatAddressFull(a)), () => ', '))), dom.tr(dom.td('Subject:', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td(dom.div(style({ display: 'flex', justifyContent: 'space-between' }), dom.div(msgenv.Subject || ''), dom.div(mi.IsSigned ? dom.span(style({ backgroundColor: '#666', padding: '0px 0.15em', fontSize: '.9em', color: 'white', borderRadius: '.15em' }), 'Message has a signature') : [], mi.IsEncrypted ? dom.span(style({ backgroundColor: '#666', padding: '0px 0.15em', fontSize: '.9em', color: 'white', borderRadius: '.15em' }), 'Message is encrypted') : [], refineKeyword ? (mi.Message.Keywords || []).map(kw => dom.clickbutton(dom._class('keyword'), kw, async function click() { await refineKeyword(kw); - })) : []))))); + })) : [])))), moreHeaders.map(k => dom.tr(dom.td(k + ':', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td()))); }; // Javascript is generated from typescript, do not modify generated javascript because changes will be overwritten. /* @@ -2392,7 +2392,8 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad }; const cmdToggleHeaders = async () => { settingsPut({ ...settings, showAllHeaders: !settings.showAllHeaders }); - loadHeaderDetails(await parsedMessagePromise); + const pm = await parsedMessagePromise; + loadHeaderDetails(pm); }; let textbtn, htmlbtn, htmlextbtn; const activeBtn = (b) => { @@ -2497,7 +2498,7 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad }))); }; loadButtons(parsedMessageOpt || null); - loadMsgheaderView(msgheaderElem, miv.messageitem, refineKeyword); + loadMsgheaderView(msgheaderElem, miv.messageitem, settings.showHeaders, refineKeyword); const loadHeaderDetails = (pm) => { if (msgheaderdetailsElem) { msgheaderdetailsElem.remove(); @@ -2621,15 +2622,34 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad urlType = 'htmlexternal'; dom._kids(msgcontentElem, dom.iframe(attr.tabindex('0'), attr.title('HTML version of message with images inlined and with external resources loaded.'), attr.src('msg/' + m.ID + '/' + urlType), style({ border: '0', position: 'absolute', width: '100%', height: '100%', backgroundColor: 'white' }))); }; + const loadMoreHeaders = (pm) => { + if (settings.showHeaders.length === 0) { + return; + } + for (let i = 0; i < settings.showHeaders.length; i++) { + msgheaderElem.children[msgheaderElem.children.length - 1].remove(); + } + settings.showHeaders.forEach(k => { + const vl = pm.Headers?.[k]; + if (!vl || vl.length === 0) { + return; + } + vl.forEach(v => { + const e = dom.tr(dom.td(k + ':', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td(v)); + msgheaderElem.appendChild(e); + }); + }); + }; const mv = { root: root, messageitem: mi, key: keyHandler(shortcuts), aborter: { abort: () => { } }, - updateKeywords: (modseq, keywords) => { + updateKeywords: async (modseq, keywords) => { mi.Message.ModSeq = modseq; mi.Message.Keywords = keywords; - loadMsgheaderView(msgheaderElem, miv.messageitem, refineKeyword); + loadMsgheaderView(msgheaderElem, miv.messageitem, settings.showHeaders, refineKeyword); + loadMoreHeaders(await parsedMessagePromise); }, }; (async () => { @@ -2656,18 +2676,7 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad } loadButtons(pm); loadHeaderDetails(pm); - if (settings.showHeaders.length > 0) { - settings.showHeaders.forEach(k => { - const vl = pm.Headers?.[k]; - if (!vl || vl.length === 0) { - return; - } - vl.forEach(v => { - const e = dom.tr(dom.td(k + ':', style({ textAlign: 'right', color: '#555', whiteSpace: 'nowrap' })), dom.td(v)); - msgheaderElem.appendChild(e); - }); - }); - } + loadMoreHeaders(pm); const htmlNote = 'In the HTML viewer, the following potentially dangerous functionality is disabled: submitting forms, starting a download from a link, navigating away from this page by clicking a link. If a link does not work, try explicitly opening it in a new tab.'; const haveText = pm.Texts && pm.Texts.length > 0; if (!haveText && !pm.HasHTML) { diff --git a/webmail/webmail.ts b/webmail/webmail.ts index 11311dc..777b88a 100644 --- a/webmail/webmail.ts +++ b/webmail/webmail.ts @@ -1741,7 +1741,7 @@ interface MsgView { root: HTMLElement messageitem: api.MessageItem // Called when keywords for a message have changed, to rerender them. - updateKeywords: (modseq: number, keywords: string[]) => void + updateKeywords: (modseq: number, keywords: string[]) => Promise // Abort loading the message. aborter: { abort: () => void } key: (key: string, e: KeyboardEvent) => Promise @@ -1841,7 +1841,8 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l const cmdToggleHeaders = async () => { settingsPut({...settings, showAllHeaders: !settings.showAllHeaders}) - loadHeaderDetails(await parsedMessagePromise) + const pm = await parsedMessagePromise + loadHeaderDetails(pm) } let textbtn: HTMLButtonElement, htmlbtn: HTMLButtonElement, htmlextbtn: HTMLButtonElement @@ -2004,7 +2005,7 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l } loadButtons(parsedMessageOpt || null) - loadMsgheaderView(msgheaderElem, miv.messageitem, refineKeyword) + loadMsgheaderView(msgheaderElem, miv.messageitem, settings.showHeaders, refineKeyword) const loadHeaderDetails = (pm: api.ParsedMessage) => { if (msgheaderdetailsElem) { @@ -2235,15 +2236,38 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l ) } + const loadMoreHeaders = (pm: api.ParsedMessage) => { + if (settings.showHeaders.length === 0) { + return + } + for (let i = 0; i < settings.showHeaders.length; i++) { + msgheaderElem.children[msgheaderElem.children.length-1].remove() + } + settings.showHeaders.forEach(k => { + const vl = pm.Headers?.[k] + if (!vl || vl.length === 0) { + return + } + vl.forEach(v => { + const e = dom.tr( + dom.td(k+':', style({textAlign: 'right', color: '#555', whiteSpace: 'nowrap'})), + dom.td(v), + ) + msgheaderElem.appendChild(e) + }) + }) + } + const mv: MsgView = { root: root, messageitem: mi, key: keyHandler(shortcuts), aborter: { abort: () => {} }, - updateKeywords: (modseq: number, keywords: string[]) => { + updateKeywords: async (modseq: number, keywords: string[]) => { mi.Message.ModSeq = modseq mi.Message.Keywords = keywords - loadMsgheaderView(msgheaderElem, miv.messageitem, refineKeyword) + loadMsgheaderView(msgheaderElem, miv.messageitem, settings.showHeaders, refineKeyword) + loadMoreHeaders(await parsedMessagePromise) }, } @@ -2269,21 +2293,7 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l loadButtons(pm) loadHeaderDetails(pm) - if (settings.showHeaders.length > 0) { - settings.showHeaders.forEach(k => { - const vl = pm.Headers?.[k] - if (!vl || vl.length === 0) { - return - } - vl.forEach(v => { - const e = dom.tr( - dom.td(k+':', style({textAlign: 'right', color: '#555', whiteSpace: 'nowrap'})), - dom.td(v), - ) - msgheaderElem.appendChild(e) - }) - }) - } + loadMoreHeaders(pm) const htmlNote = 'In the HTML viewer, the following potentially dangerous functionality is disabled: submitting forms, starting a download from a link, navigating away from this page by clicking a link. If a link does not work, try explicitly opening it in a new tab.' const haveText = pm.Texts && pm.Texts.length > 0