mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 16:33:47 +03:00
webmail: show all images (inline and attachment) below the text part (for the text view, not for html view)
the attachment buttons for images get some opacity for the text view, to indicate you don't have to open them explicitly.
This commit is contained in:
parent
41a62de4d7
commit
3a58b2a1f4
10 changed files with 133 additions and 51 deletions
|
@ -22,6 +22,18 @@ const join = (l: any, efn: () => any): any[] => {
|
|||
return r
|
||||
}
|
||||
|
||||
// From https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types
|
||||
const imageTypes = [
|
||||
'image/avif',
|
||||
'image/webp',
|
||||
'image/gif',
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/apng',
|
||||
'image/svg+xml',
|
||||
]
|
||||
const isImage = (a: api.Attachment) => imageTypes.includes((a.Part.MediaType + '/' + a.Part.MediaSubType).toLowerCase())
|
||||
|
||||
// addLinks turns a line of text into alternating strings and links. Links that
|
||||
// would end with interpunction followed by whitespace are returned with that
|
||||
// interpunction moved to the next string instead.
|
||||
|
|
|
@ -1009,6 +1009,17 @@ const join = (l, efn) => {
|
|||
}
|
||||
return r;
|
||||
};
|
||||
// From https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types
|
||||
const imageTypes = [
|
||||
'image/avif',
|
||||
'image/webp',
|
||||
'image/gif',
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/apng',
|
||||
'image/svg+xml',
|
||||
];
|
||||
const isImage = (a) => imageTypes.includes((a.Part.MediaType + '/' + a.Part.MediaSubType).toLowerCase());
|
||||
// addLinks turns a line of text into alternating strings and links. Links that
|
||||
// would end with interpunction followed by whitespace are returned with that
|
||||
// interpunction moved to the next string instead.
|
||||
|
|
|
@ -7,11 +7,14 @@
|
|||
<style>
|
||||
* { font-size: inherit; font-family: 'ubuntu', 'lato', sans-serif; margin: 0; padding: 0; box-sizing: border-box; }
|
||||
.mono, .mono * { font-family: 'ubuntu mono', monospace; }
|
||||
.pad { padding: 1ex; }
|
||||
.scriptswitch { text-decoration: underline #dca053 2px; }
|
||||
.quoted1 { color: #03828f; }
|
||||
.quoted2 { color: #c7445c; }
|
||||
.quoted3 { color: #417c10; }
|
||||
.textmulti > *:nth-child(even) { background-color: #f4f4f4; }
|
||||
.textmulti > * { padding: 2ex .5em; margin: -.5em; /* compensate pad */ }
|
||||
.textmulti > *:first-child { padding: .5em; }
|
||||
.pad { padding: .5em; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -1009,6 +1009,17 @@ const join = (l, efn) => {
|
|||
}
|
||||
return r;
|
||||
};
|
||||
// From https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types
|
||||
const imageTypes = [
|
||||
'image/avif',
|
||||
'image/webp',
|
||||
'image/gif',
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/apng',
|
||||
'image/svg+xml',
|
||||
];
|
||||
const isImage = (a) => imageTypes.includes((a.Part.MediaType + '/' + a.Part.MediaSubType).toLowerCase());
|
||||
// addLinks turns a line of text into alternating strings and links. Links that
|
||||
// would end with interpunction followed by whitespace are returned with that
|
||||
// interpunction moved to the next string instead.
|
||||
|
@ -1186,7 +1197,11 @@ const loadMsgheaderView = (msgheaderelem, mi, moreHeaders, refineKeyword, allAdd
|
|||
// Javascript is generated from typescript, do not modify generated javascript because changes will be overwritten.
|
||||
const init = async () => {
|
||||
const pm = api.parser.ParsedMessage(parsedMessage);
|
||||
dom._kids(document.body, dom.div(dom._class('pad', 'mono'), style({ whiteSpace: 'pre-wrap' }), join((pm.Texts || []).map(t => renderText(t)), () => dom.hr(style({ margin: '2ex 0' })))));
|
||||
const mi = api.parser.MessageItem(messageItem);
|
||||
dom._kids(document.body, dom.div(dom._class('pad', 'mono', 'textmulti'), style({ whiteSpace: 'pre-wrap' }), (pm.Texts || []).map(t => renderText(t.replace(/\r\n/g, '\n'))), (mi.Attachments || []).filter(f => isImage(f)).map(f => {
|
||||
const pathStr = [0].concat(f.Path || []).join('.');
|
||||
return dom.div(dom.div(style({ flexGrow: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', maxHeight: 'calc(100% - 50px)' }), dom.img(attr.src('view/' + pathStr), attr.title(f.Filename), style({ backgroundColor: 'white', maxWidth: '100%', maxHeight: '100%', boxShadow: '0 0 20px rgba(0, 0, 0, 0.1)' }))));
|
||||
})));
|
||||
};
|
||||
init()
|
||||
.catch((err) => {
|
||||
|
|
|
@ -1,14 +1,29 @@
|
|||
// Javascript is generated from typescript, do not modify generated javascript because changes will be overwritten.
|
||||
|
||||
// Loaded from synchronous javascript.
|
||||
declare let messageItem: api.MessageItem
|
||||
declare let parsedMessage: api.ParsedMessage
|
||||
|
||||
const init = async () => {
|
||||
const pm = api.parser.ParsedMessage(parsedMessage)
|
||||
const mi = api.parser.MessageItem(messageItem)
|
||||
dom._kids(document.body,
|
||||
dom.div(dom._class('pad', 'mono'),
|
||||
dom.div(dom._class('pad', 'mono', 'textmulti'),
|
||||
style({whiteSpace: 'pre-wrap'}),
|
||||
join((pm.Texts || []).map(t => renderText(t)), () => dom.hr(style({margin: '2ex 0'}))),
|
||||
(pm.Texts || []).map(t => renderText(t.replace(/\r\n/g, '\n'))),
|
||||
(mi.Attachments || []).filter(f => isImage(f)).map(f => {
|
||||
const pathStr = [0].concat(f.Path || []).join('.')
|
||||
return dom.div(
|
||||
dom.div(
|
||||
style({flexGrow: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', maxHeight: 'calc(100% - 50px)'}),
|
||||
dom.img(
|
||||
attr.src('view/'+pathStr),
|
||||
attr.title(f.Filename),
|
||||
style({backgroundColor: 'white', maxWidth: '100%', maxHeight: '100%', boxShadow: '0 0 20px rgba(0, 0, 0, 0.1)'})
|
||||
),
|
||||
)
|
||||
)
|
||||
}),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -377,7 +377,7 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt
|
|||
// allowed. Used to display a message including header. The header is rendered with
|
||||
// javascript, the content is rendered in a separate iframe with a CSP that doesn't
|
||||
// have allowSelfScript.
|
||||
headers := func(sameOrigin, allowExternal, allowSelfScript bool) {
|
||||
headers := func(sameOrigin, allowExternal, allowSelfScript, allowSelfImg bool) {
|
||||
// allow-popups is needed to make opening links in new tabs work.
|
||||
sb := "sandbox allow-popups allow-popups-to-escape-sandbox; "
|
||||
if sameOrigin && allowSelfScript {
|
||||
|
@ -394,6 +394,8 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt
|
|||
var csp string
|
||||
if allowExternal {
|
||||
csp = sb + "frame-ancestors 'self'; default-src 'none'; img-src data: http: https: 'unsafe-inline'; style-src 'unsafe-inline' data: http: https:; font-src data: http: https: 'unsafe-inline'; media-src 'unsafe-inline' data: http: https:" + script
|
||||
} else if allowSelfImg {
|
||||
csp = sb + "frame-ancestors 'self'; default-src 'none'; img-src data: 'self'; style-src 'unsafe-inline'" + script
|
||||
} else {
|
||||
csp = sb + "frame-ancestors 'self'; default-src 'none'; img-src data:; style-src 'unsafe-inline'" + script
|
||||
}
|
||||
|
@ -416,7 +418,7 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt
|
|||
mi, err := messageItem(log, m, &state)
|
||||
xcheckf(ctx, err, "parsing message")
|
||||
|
||||
headers(false, false, false)
|
||||
headers(false, false, false, false)
|
||||
h.Set("Content-Type", "application/zip")
|
||||
h.Set("Cache-Control", "no-store, max-age=0")
|
||||
var subjectSlug string
|
||||
|
@ -536,7 +538,7 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt
|
|||
// browsers or users would think of executing. We do set the charset if available
|
||||
// on the outer part. If present, we assume it may be relevant for other parts. If
|
||||
// not, there is not much we could do better...
|
||||
headers(false, false, false)
|
||||
headers(false, false, false, false)
|
||||
ct := "text/plain"
|
||||
params := map[string]string{}
|
||||
if charset := p.ContentTypeParams["charset"]; charset != "" {
|
||||
|
@ -571,7 +573,7 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt
|
|||
sameorigin := true
|
||||
loadExternal := t[1] == "msghtmlexternal"
|
||||
allowSelfScript := true
|
||||
headers(sameorigin, loadExternal, allowSelfScript)
|
||||
headers(sameorigin, loadExternal, allowSelfScript, false)
|
||||
h.Set("Content-Type", "text/html; charset=utf-8")
|
||||
h.Set("Cache-Control", "no-store, max-age=0")
|
||||
|
||||
|
@ -604,7 +606,7 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt
|
|||
mijson, err := json.Marshal(mi)
|
||||
xcheckf(ctx, err, "marshal messageitem")
|
||||
|
||||
headers(false, false, false)
|
||||
headers(false, false, false, false)
|
||||
h.Set("Content-Type", "application/javascript; charset=utf-8")
|
||||
h.Set("Cache-Control", "no-store, max-age=0")
|
||||
|
||||
|
@ -636,7 +638,8 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt
|
|||
// Needed for inner document height for outer iframe height in separate message view.
|
||||
sameorigin := true
|
||||
allowSelfScript := true
|
||||
headers(sameorigin, false, allowSelfScript)
|
||||
allowSelfImg := true
|
||||
headers(sameorigin, false, allowSelfScript, allowSelfImg)
|
||||
h.Set("Content-Type", "text/html; charset=utf-8")
|
||||
h.Set("Cache-Control", "no-store, max-age=0")
|
||||
|
||||
|
@ -662,7 +665,7 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt
|
|||
// inner height so we load it as different origin, which should be safer.
|
||||
sameorigin := r.URL.Query().Get("sameorigin") == "true"
|
||||
allowExternal := strings.HasSuffix(t[1], "external")
|
||||
headers(sameorigin, allowExternal, false)
|
||||
headers(sameorigin, allowExternal, false, false)
|
||||
|
||||
h.Set("Content-Type", "text/html; charset=utf-8")
|
||||
h.Set("Cache-Control", "no-store, max-age=0")
|
||||
|
@ -724,7 +727,7 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt
|
|||
ap = ap.Parts[int(index)]
|
||||
}
|
||||
|
||||
headers(false, false, false)
|
||||
headers(false, false, false, false)
|
||||
var ct string
|
||||
if t[1] == "viewtext" {
|
||||
ct = "text/plain"
|
||||
|
|
|
@ -27,9 +27,12 @@ button.keyword { cursor: pointer; }
|
|||
.btngroup button:first-child, .btngroup .button:first-child { border-radius: .15em 0 0 .15em; }
|
||||
.btngroup button:last-child, .btngroup .button:last-child { border-radius: 0 .15em .15em 0; border-right-width: 1px; }
|
||||
iframe { border: 0; }
|
||||
.pad { padding: .5em; }
|
||||
.invert { filter: invert(100%); }
|
||||
.scriptswitch { text-decoration: underline #dca053 2px; }
|
||||
.textmulti > *:nth-child(even) { background-color: #f4f4f4; }
|
||||
.textmulti > * { padding: 2ex .5em; margin: -.5em; /* compensate pad */ }
|
||||
.textmulti > *:first-child { padding: .5em; }
|
||||
.pad { padding: .5em; }
|
||||
|
||||
.msgitem { display: flex; user-select: none; }
|
||||
.msgitemcell { padding: 2px 4px; }
|
||||
|
|
|
@ -1009,6 +1009,17 @@ const join = (l, efn) => {
|
|||
}
|
||||
return r;
|
||||
};
|
||||
// From https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types
|
||||
const imageTypes = [
|
||||
'image/avif',
|
||||
'image/webp',
|
||||
'image/gif',
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/apng',
|
||||
'image/svg+xml',
|
||||
];
|
||||
const isImage = (a) => imageTypes.includes((a.Part.MediaType + '/' + a.Part.MediaSubType).toLowerCase());
|
||||
// addLinks turns a line of text into alternating strings and links. Links that
|
||||
// would end with interpunction followed by whitespace are returned with that
|
||||
// interpunction moved to the next string instead.
|
||||
|
@ -3565,18 +3576,7 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad
|
|||
msgheaderdetailsElem = dom.table(style({ marginBottom: '1ex', width: '100%' }), Object.entries(pm.Headers || {}).sort().map(t => (t[1] || []).map(v => dom.tr(dom.td(t[0] + ':', style({ textAlign: 'right', color: '#555' })), dom.td(v)))));
|
||||
msgattachmentElem.parentNode.insertBefore(msgheaderdetailsElem, msgattachmentElem);
|
||||
};
|
||||
// From https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types
|
||||
const imageTypes = [
|
||||
'image/avif',
|
||||
'image/webp',
|
||||
'image/gif',
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/apng',
|
||||
'image/svg+xml',
|
||||
];
|
||||
const isText = (a) => ['text', 'message'].includes(a.Part.MediaType.toLowerCase());
|
||||
const isImage = (a) => imageTypes.includes((a.Part.MediaType + '/' + a.Part.MediaSubType).toLowerCase());
|
||||
const isPDF = (a) => (a.Part.MediaType + '/' + a.Part.MediaSubType).toLowerCase() === 'application/pdf';
|
||||
const isViewable = (a) => isText(a) || isImage(a) || isPDF(a);
|
||||
const attachments = (mi.Attachments || []);
|
||||
|
@ -3643,9 +3643,10 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad
|
|||
popupRoot.focus();
|
||||
attachmentView = { key: keyHandler(attachShortcuts) };
|
||||
};
|
||||
const renderAttachments = (all) => {
|
||||
var filesAll = false;
|
||||
const renderAttachments = () => {
|
||||
const l = mi.Attachments || [];
|
||||
dom._kids(msgattachmentElem, (l && l.length === 0) ? [] : dom.div(style({ borderTop: '1px solid #ccc' }), dom.div(dom._class('pad'), 'Attachments: ', l.slice(0, all ? l.length : 4).map(a => {
|
||||
dom._kids(msgattachmentElem, (l && l.length === 0) ? [] : dom.div(style({ borderTop: '1px solid #ccc' }), dom.div(dom._class('pad'), 'Attachments: ', l.slice(0, filesAll ? l.length : 4).map(a => {
|
||||
const name = a.Filename || '(unnamed)';
|
||||
const viewable = isViewable(a);
|
||||
const size = formatSize(a.Part.DecodedSize);
|
||||
|
@ -3657,14 +3658,15 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad
|
|||
});
|
||||
const dlbtn = dom.a(dom._class('button'), attr.download(''), attr.href(dlurl), dl, viewable ? style({ padding: '0px 0.25em' }) : ' ' + name, attr.title('Download this file. Size: ' + size), style({ lineHeight: '1.5' }));
|
||||
if (viewable) {
|
||||
return [dom.span(dom._class('btngroup'), viewbtn, dlbtn), ' '];
|
||||
return [dom.span(dom._class('btngroup'), urlType === 'text' && isImage(a) ? style({ opacity: '.6' }) : [], viewbtn, dlbtn), ' '];
|
||||
}
|
||||
return [dom.span(dom._class('btngroup'), dlbtn, viewbtn), ' '];
|
||||
}), all || l.length < 6 ? [] : dom.clickbutton('More...', function click() {
|
||||
renderAttachments(true);
|
||||
}), filesAll || l.length < 6 ? [] : dom.clickbutton('More...', function click() {
|
||||
filesAll = true;
|
||||
renderAttachments();
|
||||
}), ' ', dom.a('Download all as zip', attr.download(''), style({ color: 'inherit' }), attr.href('msg/' + m.ID + '/attachments.zip')))));
|
||||
};
|
||||
renderAttachments(false);
|
||||
renderAttachments();
|
||||
const root = dom.div(style({ position: 'absolute', top: 0, right: 0, bottom: 0, left: 0, display: 'flex', flexDirection: 'column' }));
|
||||
dom._kids(root, msgmetaElem, msgcontentElem);
|
||||
const loadText = (pm) => {
|
||||
|
@ -3672,18 +3674,24 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad
|
|||
// text to use when writing a reply. We still set url so the text content can be
|
||||
// opened in a separate tab, even though it will look differently.
|
||||
urlType = 'text';
|
||||
const elem = dom.div(dom._class('mono'), style({ whiteSpace: 'pre-wrap' }), join((pm.Texts || []).map(t => renderText(t.replace(/\r\n/g, '\n'))), () => dom.hr(style({ margin: '2ex 0' }))));
|
||||
const elem = dom.div(dom._class('mono', 'textmulti'), style({ whiteSpace: 'pre-wrap' }), (pm.Texts || []).map(t => renderText(t.replace(/\r\n/g, '\n'))), (mi.Attachments || []).filter(f => isImage(f)).map(f => {
|
||||
const pathStr = [0].concat(f.Path || []).join('.');
|
||||
return dom.div(dom.div(style({ flexGrow: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', maxHeight: 'calc(100% - 50px)' }), dom.img(attr.src('msg/' + m.ID + '/view/' + pathStr), attr.title(f.Filename), style({ backgroundColor: 'white', maxWidth: '100%', maxHeight: '100%', boxShadow: '0 0 20px rgba(0, 0, 0, 0.1)' }))));
|
||||
}));
|
||||
dom._kids(msgcontentElem);
|
||||
dom._kids(msgscrollElem, elem);
|
||||
dom._kids(msgcontentElem, msgscrollElem);
|
||||
renderAttachments(); // Rerender opaciy on inline images.
|
||||
};
|
||||
const loadHTML = () => {
|
||||
urlType = 'html';
|
||||
dom._kids(msgcontentElem, dom.iframe(attr.tabindex('0'), attr.title('HTML version of message with images inlined, without external resources loaded.'), attr.src('msg/' + m.ID + '/' + urlType), style({ border: '0', position: 'absolute', width: '100%', height: '100%', backgroundColor: 'white' })));
|
||||
renderAttachments(); // Rerender opaciy on inline images.
|
||||
};
|
||||
const loadHTMLexternal = () => {
|
||||
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' })));
|
||||
renderAttachments(); // Rerender opaciy on inline images.
|
||||
};
|
||||
const loadMoreHeaders = (pm) => {
|
||||
if (settings.showHeaders.length === 0) {
|
||||
|
|
|
@ -3065,18 +3065,7 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l
|
|||
msgattachmentElem.parentNode!.insertBefore(msgheaderdetailsElem, msgattachmentElem)
|
||||
}
|
||||
|
||||
// From https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types
|
||||
const imageTypes = [
|
||||
'image/avif',
|
||||
'image/webp',
|
||||
'image/gif',
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/apng',
|
||||
'image/svg+xml',
|
||||
]
|
||||
const isText = (a: api.Attachment) => ['text', 'message'].includes(a.Part.MediaType.toLowerCase())
|
||||
const isImage = (a: api.Attachment) => imageTypes.includes((a.Part.MediaType + '/' + a.Part.MediaSubType).toLowerCase())
|
||||
const isPDF = (a: api.Attachment) => (a.Part.MediaType+'/'+a.Part.MediaSubType).toLowerCase() === 'application/pdf'
|
||||
const isViewable = (a: api.Attachment) => isText(a) || isImage(a) || isPDF(a)
|
||||
const attachments: api.Attachment[] = (mi.Attachments || [])
|
||||
|
@ -3215,14 +3204,15 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l
|
|||
attachmentView = {key: keyHandler(attachShortcuts)}
|
||||
}
|
||||
|
||||
const renderAttachments = (all: boolean) => {
|
||||
var filesAll = false
|
||||
const renderAttachments = () => {
|
||||
const l = mi.Attachments || []
|
||||
dom._kids(msgattachmentElem,
|
||||
(l && l.length === 0) ? [] : dom.div(
|
||||
style({borderTop: '1px solid #ccc'}),
|
||||
dom.div(dom._class('pad'),
|
||||
'Attachments: ',
|
||||
l.slice(0, all ? l.length : 4).map(a => {
|
||||
l.slice(0, filesAll ? l.length : 4).map(a => {
|
||||
const name = a.Filename || '(unnamed)'
|
||||
const viewable = isViewable(a)
|
||||
const size = formatSize(a.Part.DecodedSize)
|
||||
|
@ -3234,19 +3224,20 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l
|
|||
})
|
||||
const dlbtn = dom.a(dom._class('button'), attr.download(''), attr.href(dlurl), dl, viewable ? style({padding: '0px 0.25em'}) : ' '+name, attr.title('Download this file. Size: '+size), style({lineHeight: '1.5'}))
|
||||
if (viewable) {
|
||||
return [dom.span(dom._class('btngroup'), viewbtn, dlbtn), ' ']
|
||||
return [dom.span(dom._class('btngroup'), urlType === 'text' && isImage(a) ? style({opacity: '.6'}) : [], viewbtn, dlbtn), ' ']
|
||||
}
|
||||
return [dom.span(dom._class('btngroup'), dlbtn, viewbtn), ' ']
|
||||
}),
|
||||
all || l.length < 6 ? [] : dom.clickbutton('More...', function click() {
|
||||
renderAttachments(true)
|
||||
filesAll || l.length < 6 ? [] : dom.clickbutton('More...', function click() {
|
||||
filesAll = true
|
||||
renderAttachments()
|
||||
}), ' ',
|
||||
dom.a('Download all as zip', attr.download(''), style({color: 'inherit'}), attr.href('msg/'+m.ID+'/attachments.zip')),
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
renderAttachments(false)
|
||||
renderAttachments()
|
||||
|
||||
const root = dom.div(style({position: 'absolute', top: 0, right: 0, bottom: 0, left: 0, display: 'flex', flexDirection: 'column'}))
|
||||
dom._kids(root, msgmetaElem, msgcontentElem)
|
||||
|
@ -3256,13 +3247,27 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l
|
|||
// text to use when writing a reply. We still set url so the text content can be
|
||||
// opened in a separate tab, even though it will look differently.
|
||||
urlType = 'text'
|
||||
const elem = dom.div(dom._class('mono'),
|
||||
const elem = dom.div(dom._class('mono', 'textmulti'),
|
||||
style({whiteSpace: 'pre-wrap'}),
|
||||
join((pm.Texts || []).map(t => renderText(t.replace(/\r\n/g, '\n'))), () => dom.hr(style({margin: '2ex 0'}))),
|
||||
(pm.Texts || []).map(t => renderText(t.replace(/\r\n/g, '\n'))),
|
||||
(mi.Attachments || []).filter(f => isImage(f)).map(f => {
|
||||
const pathStr = [0].concat(f.Path || []).join('.')
|
||||
return dom.div(
|
||||
dom.div(
|
||||
style({flexGrow: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', maxHeight: 'calc(100% - 50px)'}),
|
||||
dom.img(
|
||||
attr.src('msg/'+m.ID+'/view/'+pathStr),
|
||||
attr.title(f.Filename),
|
||||
style({backgroundColor: 'white', maxWidth: '100%', maxHeight: '100%', boxShadow: '0 0 20px rgba(0, 0, 0, 0.1)'})
|
||||
),
|
||||
)
|
||||
)
|
||||
}),
|
||||
)
|
||||
dom._kids(msgcontentElem)
|
||||
dom._kids(msgscrollElem, elem)
|
||||
dom._kids(msgcontentElem, msgscrollElem)
|
||||
renderAttachments() // Rerender opaciy on inline images.
|
||||
}
|
||||
const loadHTML = (): void => {
|
||||
urlType = 'html'
|
||||
|
@ -3274,6 +3279,7 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l
|
|||
style({border: '0', position: 'absolute', width: '100%', height: '100%', backgroundColor: 'white'}),
|
||||
)
|
||||
)
|
||||
renderAttachments() // Rerender opaciy on inline images.
|
||||
}
|
||||
const loadHTMLexternal = (): void => {
|
||||
urlType = 'htmlexternal'
|
||||
|
@ -3285,6 +3291,7 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l
|
|||
style({border: '0', position: 'absolute', width: '100%', height: '100%', backgroundColor: 'white'}),
|
||||
)
|
||||
)
|
||||
renderAttachments() // Rerender opaciy on inline images.
|
||||
}
|
||||
|
||||
const loadMoreHeaders = (pm: api.ParsedMessage) => {
|
||||
|
|
|
@ -530,6 +530,11 @@ func TestWebmail(t *testing.T) {
|
|||
"Content-Security-Policy",
|
||||
"frame-ancestors 'self'; default-src 'none'; img-src data:; style-src 'unsafe-inline'; script-src 'unsafe-inline' 'self'; frame-src 'self'; connect-src 'self'",
|
||||
}
|
||||
// Text and img-src 'self', for viewing image files inline.
|
||||
cspTextImg := [2]string{
|
||||
"Content-Security-Policy",
|
||||
"frame-ancestors 'self'; default-src 'none'; img-src data: 'self'; style-src 'unsafe-inline'; script-src 'unsafe-inline' 'self'; frame-src 'self'; connect-src 'self'",
|
||||
}
|
||||
// HTML as viewed in the regular viewer, not in a new tab.
|
||||
cspHTML := [2]string{
|
||||
"Content-Security-Policy",
|
||||
|
@ -560,7 +565,7 @@ func TestWebmail(t *testing.T) {
|
|||
"Content-Security-Policy",
|
||||
"frame-ancestors 'self'; default-src 'none'; img-src data: http: https: 'unsafe-inline'; style-src 'unsafe-inline' data: http: https:; font-src data: http: https: 'unsafe-inline'; media-src 'unsafe-inline' data: http: https:; script-src 'unsafe-inline' 'self'; frame-src 'self'; connect-src 'self'",
|
||||
}
|
||||
testHTTPAuthREST("GET", pathInboxAltRel+"/text", http.StatusOK, httpHeaders{ctHTML, cspText}, nil)
|
||||
testHTTPAuthREST("GET", pathInboxAltRel+"/text", http.StatusOK, httpHeaders{ctHTML, cspTextImg}, nil)
|
||||
testHTTPAuthREST("GET", pathInboxAltRel+"/html", http.StatusOK, httpHeaders{ctHTML, cspHTML}, nil)
|
||||
testHTTPAuthREST("GET", pathInboxAltRel+"/htmlexternal", http.StatusOK, httpHeaders{ctHTML, cspHTMLExternal}, nil)
|
||||
testHTTPAuthREST("GET", pathInboxAltRel+"/msgtext", http.StatusOK, httpHeaders{ctHTML, cspText}, nil)
|
||||
|
|
Loading…
Reference in a new issue