plugins/base: allow move/delete/flag operations to take multiple UIDs

This commit is contained in:
Simon Ser 2020-03-19 16:43:27 +01:00
parent fe73f2022c
commit b61e40f363
No known key found for this signature in database
GPG key ID: 0FDE7BE0E88F5E48
6 changed files with 81 additions and 39 deletions

14
go.mod
View file

@ -5,8 +5,9 @@ go 1.13
require (
github.com/aymerick/douceur v0.2.0
github.com/chris-ramon/douceur v0.2.0
github.com/emersion/go-ical v0.0.0-20200225182515-5cc64a0054ad // indirect
github.com/emersion/go-imap v1.0.3
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/emersion/go-ical v0.0.0-20200225233454-26ef720b8bf1 // indirect
github.com/emersion/go-imap v1.0.4
github.com/emersion/go-imap-metadata v0.0.0-20200128185110-9d939d2a0915
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342
github.com/emersion/go-imap-specialuse v0.0.0-20161227184202-ba031ced6a62
@ -17,14 +18,13 @@ require (
github.com/emersion/go-webdav v0.2.1-0.20200227113614-abadf534f49a
github.com/google/uuid v1.1.1
github.com/gorilla/css v1.0.0 // indirect
github.com/labstack/echo/v4 v4.1.15-0.20200203180927-504f39abaf32
github.com/labstack/echo/v4 v4.1.15
github.com/labstack/gommon v0.3.0
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/microcosm-cc/bluemonday v1.0.2
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
gitlab.com/golang-commonmark/linkify v0.0.0-20200225224916-64bca66f6ad3
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d // indirect
golang.org/x/net v0.0.0-20200225223329-5d076fcf07a8
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae // indirect
golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6 // indirect
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d // indirect
layeh.com/gopher-luar v1.0.7
)

28
go.sum
View file

@ -12,10 +12,12 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
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-20200225182515-5cc64a0054ad h1:OKhKFSWeuAbVzgBsas+QCjCFLzi7pRX/FEhDudYHiQw=
github.com/emersion/go-ical v0.0.0-20200225182515-5cc64a0054ad/go.mod h1:4xVTBPcT43a1pp3vdaa+FuRdX5XhKCZPpWv7m0z9ByM=
github.com/emersion/go-ical v0.0.0-20200225233454-26ef720b8bf1 h1:v0W797seT60q3pzrphQUKh22Nt8uS7rgZyD6lqYgM0E=
github.com/emersion/go-ical v0.0.0-20200225233454-26ef720b8bf1/go.mod h1:4xVTBPcT43a1pp3vdaa+FuRdX5XhKCZPpWv7m0z9ByM=
github.com/emersion/go-imap v1.0.3 h1:5eEee8/DTSIPfliiWqwfvjPGkU8bBtvOy/Wx+eeXzO4=
github.com/emersion/go-imap v1.0.3/go.mod h1:yKASt+C3ZiDAiCSssxg9caIckWF/JG7ZQTO7GAmvicU=
github.com/emersion/go-imap v1.0.4 h1:uiCAIHM6Z5Jwkma1zdNDWWXxSCqb+/xHBkHflD7XBro=
github.com/emersion/go-imap v1.0.4/go.mod h1:yKASt+C3ZiDAiCSssxg9caIckWF/JG7ZQTO7GAmvicU=
github.com/emersion/go-imap-metadata v0.0.0-20200128185110-9d939d2a0915 h1:8xzODjLqrfAJo+CNhX0Fp47vdVN0ZvmGV3CPt/Ex1nU=
github.com/emersion/go-imap-metadata v0.0.0-20200128185110-9d939d2a0915/go.mod h1:6mXMzbK9Ts0mrrBibqy56SqZpuFMry5AedTgu6qY5zM=
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342 h1:5p1t3e1PomYgLWwEwhwEU5kVBwcyAcVrOpexv8AeZx0=
@ -39,18 +41,17 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/labstack/echo/v4 v4.1.15-0.20200203180927-504f39abaf32 h1:UiIEDYxPPmjl6mMIY4QPDguD/QAtK+gR4IRMSrjWmeA=
github.com/labstack/echo/v4 v4.1.15-0.20200203180927-504f39abaf32/go.mod h1:mbsytw7LXzfWxMLdvzjjeERCwTL3PI03GIPpUpaMnfQ=
github.com/labstack/echo/v4 v4.1.15 h1:4aE6KfJC+wCnMjODwcpeEGWGsRfszxZMwB3QVTECj2I=
github.com/labstack/echo/v4 v4.1.15/go.mod h1:GWO5IBVzI371K8XJe50CSvHjQCafK6cw8R/moLhEU6o=
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/martinlindhe/base36 v1.0.0 h1:eYsumTah144C0A8P1T/AVSUk5ZoLnhfYFM3OGQxB52A=
github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
@ -72,24 +73,25 @@ github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBU
gitlab.com/golang-commonmark/linkify v0.0.0-20200225224916-64bca66f6ad3 h1:1Coh5BsUBlXoEJmIEaNzVAWrtg9k7/eJzailMQr1grw=
gitlab.com/golang-commonmark/linkify v0.0.0-20200225224916-64bca66f6ad3/go.mod h1:Gn+LZmCrhPECMD3SOKlE+BOHwhOYD9j7WT9NUtkCrC8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6 h1:TjszyFsQsyZNHwdVdZ5m7bjmreu0znc2kRYsEml9/Ww=
golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200225223329-5d076fcf07a8 h1:0hpG1RSqNmKZ60akfzABaMjKAn2762l2+HJyXFT5LMY=
golang.org/x/net v0.0.0-20200225223329-5d076fcf07a8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d h1:62ap6LNOjDU6uGmKXHJbSfciMoV+FeI1sRXx/pLDL44=
golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=

View file

@ -16,7 +16,8 @@
{{end}}
</h2>
<form method="post" action="{{.Message.URL}}/move">
<form method="post" action="/message/{{.Mailbox.Name | pathescape}}/move">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
<label for="move-to">Move to:</label>
<select name="to" id="move-to">
{{range .Mailboxes}}
@ -26,12 +27,14 @@
<input type="submit" value="Move">
</form>
<form method="post" action="{{.Message.URL}}/delete">
<form method="post" action="/message/{{.Mailbox.Name | pathescape}}/delete">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
<input type="submit" value="Delete">
</form>
{{if .Flags}}
<form method="post" action="{{.Message.URL}}/flag">
<form method="post" action="/message/{{.Mailbox.Name | pathescape}}/flag">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
<p>Flags:</p>
{{range $name, $has := .Flags}}
{{if ismutableflag $name}}

View file

@ -53,11 +53,11 @@ func registerRoutes(p *koushin.GoPlugin) {
p.GET("/message/:mbox/:uid/edit", handleEdit)
p.POST("/message/:mbox/:uid/edit", handleEdit)
p.POST("/message/:mbox/:uid/move", handleMove)
p.POST("/message/:mbox/move", handleMove)
p.POST("/message/:mbox/:uid/delete", handleDelete)
p.POST("/message/:mbox/delete", handleDelete)
p.POST("/message/:mbox/:uid/flag", handleSetFlags)
p.POST("/message/:mbox/flag", handleSetFlags)
p.GET("/settings", handleSettings)
p.POST("/settings", handleSettings)
@ -654,7 +654,16 @@ func handleEdit(ctx *koushin.Context) error {
}
func handleMove(ctx *koushin.Context) error {
mboxName, uid, err := parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid"))
mboxName, err := url.PathUnescape(ctx.Param("mbox"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
}
formParams, err := ctx.FormParams()
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
}
uids, err := parseUidList(formParams["uids"])
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
}
@ -669,7 +678,7 @@ func handleMove(ctx *koushin.Context) error {
}
var seqSet imap.SeqSet
seqSet.AddNum(uid)
seqSet.AddNum(uids...)
if err := mc.UidMoveWithFallback(&seqSet, to); err != nil {
return fmt.Errorf("failed to move message: %v", err)
}
@ -685,7 +694,16 @@ func handleMove(ctx *koushin.Context) error {
}
func handleDelete(ctx *koushin.Context) error {
mboxName, uid, err := parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid"))
mboxName, err := url.PathUnescape(ctx.Param("mbox"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
}
formParams, err := ctx.FormParams()
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
}
uids, err := parseUidList(formParams["uids"])
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
}
@ -696,7 +714,7 @@ func handleDelete(ctx *koushin.Context) error {
}
var seqSet imap.SeqSet
seqSet.AddNum(uid)
seqSet.AddNum(uids...)
item := imap.FormatFlagsOp(imap.AddFlags, true)
flags := []interface{}{imap.DeletedFlag}
@ -724,16 +742,20 @@ func handleDelete(ctx *koushin.Context) error {
}
func handleSetFlags(ctx *koushin.Context) error {
mboxName, uid, err := parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid"))
mboxName, err := url.PathUnescape(ctx.Param("mbox"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
}
form, err := ctx.FormParams()
formParams, err := ctx.FormParams()
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
}
flags, ok := form["flags"]
uids, err := parseUidList(formParams["uids"])
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
}
flags, ok := formParams["flags"]
if !ok {
return echo.NewHTTPError(http.StatusBadRequest, "missing 'flags' form values")
}
@ -756,7 +778,7 @@ func handleSetFlags(ctx *koushin.Context) error {
}
var seqSet imap.SeqSet
seqSet.AddNum(uid)
seqSet.AddNum(uids...)
storeItems := make([]interface{}, len(flags))
for i, f := range flags {
@ -774,11 +796,11 @@ func handleSetFlags(ctx *koushin.Context) error {
return err
}
if op == imap.RemoveFlags && len(flags) == 1 && flags[0] == "\\Seen" {
if len(uids) != 1 || (op == imap.RemoveFlags && len(flags) == 1 && flags[0] == "\\Seen") {
// Redirecting to the message view would mark the message as read again
return ctx.Redirect(http.StatusFound, fmt.Sprintf("/mailbox/%v", url.PathEscape(mboxName)))
}
return ctx.Redirect(http.StatusFound, fmt.Sprintf("/message/%v/%v", url.PathEscape(mboxName), uid))
return ctx.Redirect(http.StatusFound, fmt.Sprintf("/message/%v/%v", url.PathEscape(mboxName), uids[0]))
}
const settingsKey = "base.settings"

View file

@ -27,6 +27,18 @@ func parseMboxAndUid(mboxString, uidString string) (string, uint32, error) {
return mboxName, uid, err
}
func parseUidList(values []string) ([]uint32, error) {
var uids []uint32
for _, v := range values {
uid, err := parseUid(v)
if err != nil {
return nil, err
}
uids = append(uids, uid)
}
return uids, nil
}
func parsePartPath(s string) ([]int, error) {
if s == "" {
return nil, nil

View file

@ -124,7 +124,8 @@
<details>
<summary>Move to another mailbox</summary>
<form method="post" action="{{.Message.URL}}/move">
<form method="post" action="/message/{{.Mailbox.Name | pathescape}}/move">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
<div class="form-group">
<select class="form-control" name="to" id="move-to">
{{range .Mailboxes}}
@ -140,7 +141,8 @@
<details>
<summary>Delete</summary>
<form method="post" action="{{.Message.URL}}/delete">
<form method="post" action="/message/{{.Mailbox.Name | pathescape}}/delete">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
<p>Are you sure?</p>
<div class="pull-right">
<button class="btn btn-danger">Delete</button>
@ -151,7 +153,8 @@
{{if .Flags}}
<details>
<summary>Edit flags</summary>
<form method="post" action="{{.Message.URL}}/flag">
<form method="post" action="/message/{{.Mailbox.Name | pathescape}}/flag">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
<div class="form-group">
{{range $name, $has := .Flags}}
{{if ismutableflag $name}}