@@ -6,6 +6,7 @@ require ( | |||
git.sr.ht/~sircmpwn/dowork v0.0.0-20201013174036-53a05612a082 | |||
github.com/aymerick/douceur v0.2.0 | |||
github.com/chris-ramon/douceur v0.2.0 | |||
github.com/dustin/go-humanize v1.0.0 | |||
github.com/emersion/go-ical v0.0.0-20200225233454-26ef720b8bf1 | |||
github.com/emersion/go-imap v1.0.4 | |||
github.com/emersion/go-imap-metadata v0.0.0-20200128185110-9d939d2a0915 | |||
@@ -22,6 +22,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c | |||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= | |||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | |||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= | |||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= | |||
github.com/emersion/go-ical v0.0.0-20200224201310-cd514449c39e h1:YGM1sI7edZOt8KAfX9Miq/X99d2QXdgjkJ7vN4HjxAA= | |||
github.com/emersion/go-ical v0.0.0-20200224201310-cd514449c39e/go.mod h1:4xVTBPcT43a1pp3vdaa+FuRdX5XhKCZPpWv7m0z9ByM= | |||
github.com/emersion/go-ical v0.0.0-20200225233454-26ef720b8bf1 h1:v0W797seT60q3pzrphQUKh22Nt8uS7rgZyD6lqYgM0E= | |||
@@ -10,11 +10,12 @@ import ( | |||
"strings" | |||
"time" | |||
"github.com/dustin/go-humanize" | |||
"github.com/emersion/go-imap" | |||
imapspecialuse "github.com/emersion/go-imap-specialuse" | |||
imapclient "github.com/emersion/go-imap/client" | |||
"github.com/emersion/go-message" | |||
"github.com/emersion/go-message/textproto" | |||
imapclient "github.com/emersion/go-imap/client" | |||
imapspecialuse "github.com/emersion/go-imap-specialuse" | |||
) | |||
type MailboxInfo struct { | |||
@@ -177,6 +178,7 @@ func newIMAPPartNode(msg *IMAPMessage, path []int, part *imap.BodyStructure) *IM | |||
MIMEType: strings.ToLower(part.MIMEType + "/" + part.MIMESubType), | |||
Filename: filename, | |||
Message: msg, | |||
Size: part.Size, | |||
} | |||
} | |||
@@ -301,6 +303,7 @@ type IMAPPartNode struct { | |||
Filename string | |||
Children []IMAPPartNode | |||
Message *IMAPMessage | |||
Size uint32 | |||
} | |||
func (node IMAPPartNode) PathString() string { | |||
@@ -308,10 +311,13 @@ func (node IMAPPartNode) PathString() string { | |||
for i, partNum := range node.Path { | |||
l[i] = strconv.Itoa(partNum) | |||
} | |||
return strings.Join(l, ".") | |||
} | |||
func (node IMAPPartNode) SizeString() string { | |||
return humanize.IBytes(uint64(node.Size)) | |||
} | |||
func (node IMAPPartNode) URL(raw bool) *url.URL { | |||
u := node.Message.URL() | |||
if raw { | |||
@@ -348,6 +354,7 @@ func imapPartTree(msg *IMAPMessage, bs *imap.BodyStructure, path []int) *IMAPPar | |||
Filename: filename, | |||
Children: make([]IMAPPartNode, len(bs.Parts)), | |||
Message: msg, | |||
Size: bs.Size, | |||
} | |||
for i, part := range bs.Parts { | |||
@@ -396,7 +403,12 @@ func listMessages(conn *imapclient.Client, mbox *MailboxStatus, page, messagesPe | |||
var seqSet imap.SeqSet | |||
seqSet.AddRange(uint32(from), uint32(to)) | |||
fetch := []imap.FetchItem{imap.FetchFlags, imap.FetchEnvelope, imap.FetchUid, imap.FetchBodyStructure} | |||
fetch := []imap.FetchItem{ | |||
imap.FetchFlags, | |||
imap.FetchEnvelope, | |||
imap.FetchUid, | |||
imap.FetchBodyStructure, | |||
} | |||
ch := make(chan *imap.Message, 10) | |||
done := make(chan error, 1) | |||
@@ -452,7 +464,12 @@ func searchMessages(conn *imapclient.Client, mboxName, query string, page, messa | |||
var seqSet imap.SeqSet | |||
seqSet.AddNum(nums...) | |||
fetch := []imap.FetchItem{imap.FetchEnvelope, imap.FetchFlags, imap.FetchUid, imap.FetchBodyStructure} | |||
fetch := []imap.FetchItem{ | |||
imap.FetchEnvelope, | |||
imap.FetchFlags, | |||
imap.FetchUid, | |||
imap.FetchBodyStructure, | |||
} | |||
ch := make(chan *imap.Message, 10) | |||
done := make(chan error, 1) | |||
@@ -506,6 +523,7 @@ func getMessagePart(conn *imapclient.Client, mboxName string, uid uint32, partPa | |||
imap.FetchUid, | |||
imap.FetchBodyStructure, | |||
imap.FetchFlags, | |||
imap.FetchRFC822Size, | |||
partHeaderSection.FetchItem(), | |||
partBodySection.FetchItem(), | |||
} | |||
@@ -261,14 +261,21 @@ main.message .message-header { | |||
} | |||
main.message .message-header .parts { | |||
width: 30%; | |||
margin-left: 0.3rem; | |||
padding: 0.3rem 1rem; | |||
padding: 0.3rem 0.5rem; | |||
background: white; | |||
border-top: 1px solid #f2f2f2; | |||
} | |||
main.message .message-header .parts ul { | |||
margin-left: 1rem; | |||
margin: 0; | |||
padding: 0; | |||
list-style: none; | |||
} | |||
main.message .message-header .parts li { | |||
margin-left: 0; | |||
} | |||
main.contact dl { | |||
@@ -2,32 +2,6 @@ | |||
{{template "nav.html" .}} | |||
{{template "util.html" .}} | |||
{{define "message-part-tree"}} | |||
{{/* nested templates can't access the parent's context */}} | |||
{{$ = index . 0}} | |||
{{with index . 1}} | |||
<a | |||
class="nav-link" | |||
{{if .IsText}} | |||
href="?part={{.PathString}}" | |||
{{else}} | |||
href="{{$.Message.URL}}raw?part={{.PathString}}" | |||
{{end}} | |||
> | |||
{{if eq $.Part.PathString .PathString}}<strong>{{end}} | |||
{{.String}} | |||
{{if eq $.Part.PathString .PathString}}</strong>{{end}} | |||
</a> | |||
{{if gt (len .Children) 0}} | |||
<ul class="nav flex-column"> | |||
{{range .Children}} | |||
<li class="nav-item">{{template "message-part-tree" (tuple $ .)}}</li> | |||
{{end}} | |||
</ul> | |||
{{end}} | |||
{{end}} | |||
{{end}} | |||
<div class="page-wrap"> | |||
{{ $current := .Mailbox }} | |||
{{ template "aside" . }} | |||
@@ -162,9 +136,32 @@ | |||
</tr> | |||
{{end}} | |||
</table> | |||
{{ $attachments := .Message.Attachments }} | |||
{{ if $attachments }} | |||
<section class="parts"> | |||
{{template "message-part-tree" (tuple $ .Message.PartTree)}} | |||
<h3>Attachments</h3> | |||
<ul> | |||
{{ range .Message.Attachments }} | |||
<li> | |||
<a | |||
class="nav-link" | |||
{{if .IsText}} | |||
href="?part={{.PathString}}" | |||
{{else}} | |||
href="{{$.Message.URL}}/raw?part={{.PathString}}" | |||
{{end}} | |||
> | |||
{{- if .Filename -}} | |||
{{.Filename}} | |||
{{- else -}} | |||
(no filename) | |||
{{- end -}} | |||
</a> ({{.MIMEType}}, {{.SizeString}}) | |||
</li> | |||
{{ end }} | |||
</ul> | |||
</section> | |||
{{ end }} | |||
</div> | |||
{{define "addr-list"}} | |||
@@ -197,6 +194,7 @@ | |||
{{ end }} | |||
<a href="{{.Message.URL}}/raw?plain=1">Raw email</a> | |||
</div> | |||
{{if .View}} | |||
{{.View}} | |||
{{else}} | |||