diff options
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/base/imap.go | 10 | ||||
-rw-r--r-- | plugins/base/public/message.html | 21 | ||||
-rw-r--r-- | plugins/base/routes.go | 100 | ||||
-rw-r--r-- | plugins/base/template.go | 22 |
4 files changed, 130 insertions, 23 deletions
diff --git a/plugins/base/imap.go b/plugins/base/imap.go index 3ccc73a..433a63e 100644 --- a/plugins/base/imap.go +++ b/plugins/base/imap.go @@ -174,6 +174,15 @@ func (msg *IMAPMessage) PartTree() *IMAPPartNode { return imapPartTree(msg.BodyStructure, nil) } +func (msg *IMAPMessage) HasFlag(flag string) bool { + for _, f := range msg.Flags { + if imap.CanonicalFlag(f) == flag { + return true + } + } + return false +} + func listMessages(conn *imapclient.Client, mboxName string, page int) ([]IMAPMessage, error) { if err := ensureMailboxSelected(conn, mboxName); err != nil { return nil, err @@ -295,6 +304,7 @@ func getMessagePart(conn *imapclient.Client, mboxName string, uid uint32, partPa imap.FetchEnvelope, imap.FetchUid, imap.FetchBodyStructure, + imap.FetchFlags, partHeaderSection.FetchItem(), partBodySection.FetchItem(), } diff --git a/plugins/base/public/message.html b/plugins/base/public/message.html index 0b7a8d5..56e6e6f 100644 --- a/plugins/base/public/message.html +++ b/plugins/base/public/message.html @@ -30,6 +30,25 @@ <input type="submit" value="Delete"> </form> +{{if .Flags}} + <form method="post" action="{{.Message.Uid}}/flag"> + <p>Flags:</p> + {{range $name, $has := .Flags}} + {{if ismutableflag $name}} + <input type="checkbox" name="flags" id="flag-{{$name}}" + value="{{$name}}" {{if $has}}checked{{end}}> + <label for="flag-{{$name}}">{{$name | formatflag}}</label> + <br> + {{else}} + {{if $has}} + <input type="hidden" name="flags" value="{{$name}}"> + {{end}} + {{end}} + {{end}} + <input type="submit" value="Set flags"> + </form> +{{end}} + <ul> <li> <strong>Date</strong>: {{.Message.Envelope.Date | formatdate}} @@ -67,7 +86,7 @@ {{.String}} {{if eq $.PartPath .PathString}}</strong>{{end}} </a> - {{if gt (len .Children) 0}} + {{if .Children}} <ul> {{range .Children}} <li>{{template "message-part-tree" (tuple $ .)}}</li> diff --git a/plugins/base/routes.go b/plugins/base/routes.go index c0f5817..2c8d653 100644 --- a/plugins/base/routes.go +++ b/plugins/base/routes.go @@ -18,6 +18,37 @@ import ( "github.com/labstack/echo/v4" ) +func registerRoutes(p *koushin.GoPlugin) { + p.GET("/mailbox/:mbox", handleGetMailbox) + p.POST("/mailbox/:mbox", handleGetMailbox) + + p.GET("/message/:mbox/:uid", func(ectx echo.Context) error { + ctx := ectx.(*koushin.Context) + return handleGetPart(ctx, false) + }) + p.GET("/message/:mbox/:uid/raw", func(ectx echo.Context) error { + ctx := ectx.(*koushin.Context) + return handleGetPart(ctx, true) + }) + + p.GET("/login", handleLogin) + p.POST("/login", handleLogin) + + p.GET("/logout", handleLogout) + + p.GET("/compose", handleCompose) + p.POST("/compose", handleCompose) + + p.GET("/message/:mbox/:uid/reply", handleCompose) + p.POST("/message/:mbox/:uid/reply", handleCompose) + + p.POST("/message/:mbox/:uid/move", handleMove) + + p.POST("/message/:mbox/:uid/delete", handleDelete) + + p.POST("/message/:mbox/:uid/flag", handleSetFlags) +} + type MailboxRenderData struct { koushin.RenderData Mailbox *imap.MailboxStatus @@ -127,6 +158,7 @@ type MessageRenderData struct { Body string PartPath string MailboxPage int + Flags map[string]bool } func handleGetPart(ctx *koushin.Context, raw bool) error { @@ -193,6 +225,15 @@ func handleGetPart(ctx *koushin.Context, raw bool) error { body = string(b) } + flags := make(map[string]bool) + for _, f := range mbox.PermanentFlags { + f = imap.CanonicalFlag(f) + if f == imap.TryCreateFlag { + continue + } + flags[f] = msg.HasFlag(f) + } + return ctx.Render(http.StatusOK, "message.html", &MessageRenderData{ RenderData: *koushin.NewRenderData(ctx), Mailboxes: mailboxes, @@ -201,6 +242,7 @@ func handleGetPart(ctx *koushin.Context, raw bool) error { Body: body, PartPath: partPathString, MailboxPage: int(mbox.Messages-msg.SeqNum) / messagesPerPage, + Flags: flags, }) } @@ -336,7 +378,7 @@ func handleMove(ectx echo.Context) error { return err } - return ctx.Redirect(http.StatusFound, fmt.Sprintf("/mailbox/%v", to)) + return ctx.Redirect(http.StatusFound, fmt.Sprintf("/mailbox/%v", url.PathEscape(to))) } func handleDelete(ectx echo.Context) error { @@ -377,34 +419,48 @@ func handleDelete(ectx echo.Context) error { return err } - return ctx.Redirect(http.StatusFound, fmt.Sprintf("/mailbox/%v", mboxName)) + return ctx.Redirect(http.StatusFound, fmt.Sprintf("/mailbox/%v", url.PathEscape(mboxName))) } -func registerRoutes(p *koushin.GoPlugin) { - p.GET("/mailbox/:mbox", handleGetMailbox) - p.POST("/mailbox/:mbox", handleGetMailbox) +func handleSetFlags(ectx echo.Context) error { + ctx := ectx.(*koushin.Context) - p.GET("/message/:mbox/:uid", func(ectx echo.Context) error { - ctx := ectx.(*koushin.Context) - return handleGetPart(ctx, false) - }) - p.GET("/message/:mbox/:uid/raw", func(ectx echo.Context) error { - ctx := ectx.(*koushin.Context) - return handleGetPart(ctx, true) - }) + mboxName, uid, err := parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err) + } - p.GET("/login", handleLogin) - p.POST("/login", handleLogin) + if err := ctx.Request().ParseForm(); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err) + } + flags, ok := ctx.Request().Form["flags"] + if !ok { + return echo.NewHTTPError(http.StatusBadRequest, "missing 'flags' form values") + } - p.GET("/logout", handleLogout) + err = ctx.Session.DoIMAP(func(c *imapclient.Client) error { + if err := ensureMailboxSelected(c, mboxName); err != nil { + return err + } - p.GET("/compose", handleCompose) - p.POST("/compose", handleCompose) + var seqSet imap.SeqSet + seqSet.AddNum(uid) - p.GET("/message/:mbox/:uid/reply", handleCompose) - p.POST("/message/:mbox/:uid/reply", handleCompose) + storeItems := make([]interface{}, len(flags)) + for i, f := range flags { + storeItems[i] = f + } - p.POST("/message/:mbox/:uid/move", handleMove) + item := imap.FormatFlagsOp(imap.SetFlags, true) + if err := c.UidStore(&seqSet, item, storeItems, nil); err != nil { + return fmt.Errorf("failed to add deleted flag: %v", err) + } - p.POST("/message/:mbox/:uid/delete", handleDelete) + return nil + }) + if err != nil { + return err + } + + return ctx.Redirect(http.StatusFound, fmt.Sprintf("/message/%v/%v", url.PathEscape(mboxName), uid)) } diff --git a/plugins/base/template.go b/plugins/base/template.go index 84c4e21..714ca3c 100644 --- a/plugins/base/template.go +++ b/plugins/base/template.go @@ -26,4 +26,26 @@ var templateFuncs = template.FuncMap{ "formatdate": func(t time.Time) string { return t.Format("Mon Jan 02 15:04") }, + "formatflag": func(flag string) string { + switch flag { + case imap.SeenFlag: + return "Seen" + case imap.AnsweredFlag: + return "Answered" + case imap.FlaggedFlag: + return "Starred" + case imap.DraftFlag: + return "Draft" + default: + return flag + } + }, + "ismutableflag": func(flag string) bool { + switch flag { + case imap.AnsweredFlag, imap.DeletedFlag, imap.DraftFlag: + return false + default: + return true + } + }, } |