@@ -180,6 +180,33 @@ func (msg *IMAPMessage) Attachments() []IMAPPartNode { | |||
return attachments | |||
} | |||
func pathsEqual(a, b []int) bool { | |||
if len(a) != len(b) { | |||
return false | |||
} | |||
for i := range a { | |||
if a[i] != b[i] { | |||
return false | |||
} | |||
} | |||
return true | |||
} | |||
func (msg *IMAPMessage) PartByPath(path []int) *IMAPPartNode { | |||
if msg.BodyStructure == nil { | |||
return nil | |||
} | |||
var result *IMAPPartNode | |||
msg.BodyStructure.Walk(func(p []int, part *imap.BodyStructure) bool { | |||
if result == nil && pathsEqual(path, p) { | |||
result = newIMAPPartNode(msg, p, part) | |||
} | |||
return result == nil | |||
}) | |||
return result | |||
} | |||
func (msg *IMAPMessage) PartByID(id string) *IMAPPartNode { | |||
if msg.BodyStructure == nil || id == "" { | |||
return nil | |||
@@ -16,7 +16,7 @@ | |||
{{end}} | |||
</h2> | |||
<form method="post" action="{{.Message.Uid}}/move"> | |||
<form method="post" action="{{.Message.URL}}/move"> | |||
<label for="move-to">Move to:</label> | |||
<select name="to" id="move-to"> | |||
{{range .Mailboxes}} | |||
@@ -26,12 +26,12 @@ | |||
<input type="submit" value="Move"> | |||
</form> | |||
<form method="post" action="{{.Message.Uid}}/delete"> | |||
<form method="post" action="{{.Message.URL}}/delete"> | |||
<input type="submit" value="Delete"> | |||
</form> | |||
{{if .Flags}} | |||
<form method="post" action="{{.Message.Uid}}/flag"> | |||
<form method="post" action="{{.Message.URL}}/flag"> | |||
<p>Flags:</p> | |||
{{range $name, $has := .Flags}} | |||
{{if ismutableflag $name}} | |||
@@ -85,14 +85,14 @@ | |||
{{with index . 1}} | |||
<a | |||
{{if .IsText}} | |||
href="{{$.Message.Uid}}?part={{.PathString}}" | |||
href="{{$.Message.URL}}?part={{.PathString}}" | |||
{{else}} | |||
href="{{$.Message.Uid}}/raw?part={{.PathString}}" | |||
href="{{$.Message.URL}}/raw?part={{.PathString}}" | |||
{{end}} | |||
> | |||
{{if eq $.PartPath .PathString}}<strong>{{end}} | |||
{{if eq $.Part.PathString .PathString}}<strong>{{end}} | |||
{{.String}} | |||
{{if eq $.PartPath .PathString}}</strong>{{end}} | |||
{{if eq $.Part.PathString .PathString}}</strong>{{end}} | |||
</a> | |||
{{if .Children}} | |||
<ul> | |||
@@ -113,15 +113,15 @@ | |||
{{if .View}} | |||
<p> | |||
{{if .Message.HasFlag "\\Draft"}} | |||
<a href="{{.Message.Uid}}/edit?part={{.PartPath}}">Edit draft</a> | |||
<a href="{{.Message.URL}}/edit?part={{.Part.PathString}}">Edit draft</a> | |||
{{else}} | |||
<a href="{{.Message.Uid}}/reply?part={{.PartPath}}">Reply</a> | |||
<a href="{{.Message.URL}}/reply?part={{.Part.PathString}}">Reply</a> | |||
{{end}} | |||
</p> | |||
{{.View}} | |||
{{else}} | |||
<p>Can't preview this message part.</p> | |||
<a href="{{.Message.Uid}}/raw?part={{.PartPath}}">Download</a> | |||
<a href="{{.Message.URL}}/raw?part={{.Part.PathString}}">Download</a> | |||
{{end}} | |||
{{template "foot.html"}} |
@@ -176,8 +176,8 @@ type MessageRenderData struct { | |||
Mailboxes []*imap.MailboxInfo | |||
Mailbox *imap.MailboxStatus | |||
Message *IMAPMessage | |||
Part *IMAPPartNode | |||
View interface{} | |||
PartPath string | |||
MailboxPage int | |||
Flags map[string]bool | |||
} | |||
@@ -187,8 +187,7 @@ func handleGetPart(ctx *koushin.Context, raw bool) error { | |||
if err != nil { | |||
return echo.NewHTTPError(http.StatusBadRequest, err) | |||
} | |||
partPathString := ctx.QueryParam("part") | |||
partPath, err := parsePartPath(partPathString) | |||
partPath, err := parsePartPath(ctx.QueryParam("part")) | |||
if err != nil { | |||
return echo.NewHTTPError(http.StatusBadRequest, err) | |||
} | |||
@@ -273,8 +272,8 @@ func handleGetPart(ctx *koushin.Context, raw bool) error { | |||
Mailboxes: mailboxes, | |||
Mailbox: mbox, | |||
Message: msg, | |||
Part: msg.PartByPath(partPath), | |||
View: view, | |||
PartPath: partPathString, | |||
MailboxPage: int(mbox.Messages-msg.SeqNum) / messagesPerPage, | |||
Flags: flags, | |||
}) | |||
@@ -431,6 +430,7 @@ func handleCompose(ctx *koushin.Context, msg *OutgoingMessage, draft *messagePat | |||
func handleComposeNew(ctx *koushin.Context) error { | |||
// These are common mailto URL query parameters | |||
// TODO: cc, bcc | |||
return handleCompose(ctx, &OutgoingMessage{ | |||
To: strings.Split(ctx.QueryParam("to"), ","), | |||
Subject: ctx.QueryParam("subject"), | |||
@@ -19,7 +19,7 @@ | |||
<li class="nav-item"> | |||
<a | |||
class="nav-link" | |||
href="{{.Message.Uid}}/reply?part={{.PartPath}}" | |||
href="{{.Message.URL}}/reply?part={{.Part.PathString}}" | |||
>Reply</a> | |||
</li> | |||
<li class="mr-auto d-none d-sm-flex"></li> | |||
@@ -47,14 +47,14 @@ | |||
<a | |||
class="nav-link" | |||
{{if .IsText}} | |||
href="{{$.Message.Uid}}?part={{.PathString}}" | |||
href="{{$.Message.URL}}?part={{.PathString}}" | |||
{{else}} | |||
href="{{$.Message.Uid}}/raw?part={{.PathString}}" | |||
href="{{$.Message.URL}}/raw?part={{.PathString}}" | |||
{{end}} | |||
> | |||
{{if eq $.PartPath .PathString}}<strong>{{end}} | |||
{{if eq $.Part.PathString .PathString}}<strong>{{end}} | |||
{{.String}} | |||
{{if eq $.PartPath .PathString}}</strong>{{end}} | |||
{{if eq $.Part.PathString .PathString}}</strong>{{end}} | |||
</a> | |||
{{if gt (len .Children) 0}} | |||
<ul class="nav flex-column"> | |||
@@ -102,7 +102,7 @@ | |||
{{if and .Extra.HasRemoteResources (not .Extra.RemoteResourcesAllowed)}} | |||
<p class="alert alert-info"> | |||
This message contains remote content. | |||
<a href="?part={{.PartPath}}&allow-remote-resources=1" class="alert-link">Load</a> | |||
<a href="?part={{.Part.PathString}}&allow-remote-resources=1" class="alert-link">Load</a> | |||
</p> | |||
{{end}} | |||
@@ -110,7 +110,7 @@ | |||
{{.View}} | |||
{{else}} | |||
<p>Can't preview this message part.</p> | |||
<a href="{{.Message.Uid}}/raw?part={{.PartPath}}">Download</a> | |||
<a href="{{.Message.URL}}/raw?part={{.Part.PathString}}">Download</a> | |||
{{end}} | |||
</div> | |||
<div class="col-md-3 parts-column"> | |||
@@ -118,7 +118,7 @@ | |||
<details> | |||
<summary>Move to another mailbox</summary> | |||
<form method="post" action="{{.Message.Uid}}/move"> | |||
<form method="post" action="{{.Message.URL}}/move"> | |||
<div class="form-group"> | |||
<select class="form-control" name="to" id="move-to"> | |||
{{range .Mailboxes}} | |||
@@ -134,7 +134,7 @@ | |||
<details> | |||
<summary>Delete</summary> | |||
<form method="post" action="{{.Message.Uid}}/delete"> | |||
<form method="post" action="{{.Message.URL}}/delete"> | |||
<p>Are you sure?</p> | |||
<div class="pull-right"> | |||
<button class="btn btn-danger">Delete</button> | |||
@@ -145,7 +145,7 @@ | |||
{{if .Flags}} | |||
<details> | |||
<summary>Edit flags</summary> | |||
<form method="post" action="{{.Message.Uid}}/flag"> | |||
<form method="post" action="{{.Message.URL}}/flag"> | |||
<div class="form-group"> | |||
{{range $name, $has := .Flags}} | |||
{{if ismutableflag $name}} | |||