webmail: don't lose display of additional headers when a flag/keyword changes (e.g. marked as read)

This commit is contained in:
Mechiel Lukkien 2023-08-11 08:38:57 +02:00
parent 383eb483df
commit d7df70acd8
No known key found for this signature in database
6 changed files with 70 additions and 45 deletions

View file

@ -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<void>)) => {
const loadMsgheaderView = (msgheaderelem: HTMLElement, mi: api.MessageItem, moreHeaders: string[], refineKeyword: null | ((kw: string) => Promise<void>)) => {
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(),
)
),
)
}

View file

@ -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;

View file

@ -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]

View file

@ -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 () => {

View file

@ -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) {

View file

@ -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<void>
// Abort loading the message.
aborter: { abort: () => void }
key: (key: string, e: KeyboardEvent) => Promise<void>
@ -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