@@ -1,6 +1,6 @@ | |||
# koushin | |||
# alps | |||
[![GoDoc](https://godoc.org/git.sr.ht/~emersion/koushin?status.svg)](https://godoc.org/git.sr.ht/~emersion/koushin) | |||
[![GoDoc](https://godoc.org/git.sr.ht/~emersion/alps?status.svg)](https://godoc.org/git.sr.ht/~emersion/alps) | |||
A simple and extensible webmail. | |||
@@ -8,17 +8,17 @@ A simple and extensible webmail. | |||
Assuming SRV DNS records are properly set up (see [RFC 6186]): | |||
go run ./cmd/koushin example.org | |||
go run ./cmd/alps example.org | |||
To manually specify upstream servers: | |||
go run ./cmd/koushin imaps://mail.example.org:993 smtps://mail.example.org:465 | |||
go run ./cmd/alps imaps://mail.example.org:993 smtps://mail.example.org:465 | |||
Add `-theme sourcehut` to use the SourceHut theme. See `docs/cli.md` for more | |||
information. | |||
When developing themes and plugins, the script `contrib/hotreload.sh` can be | |||
used to automatically reload koushin on file changes. | |||
used to automatically reload alps on file changes. | |||
## Contributing | |||
@@ -29,6 +29,6 @@ Send patches on the [mailing list], report bugs on the [issue tracker]. | |||
MIT | |||
[RFC 6186]: https://tools.ietf.org/html/rfc6186 | |||
[Go plugin helpers]: https://godoc.org/git.sr.ht/~emersion/koushin#GoPlugin | |||
[mailing list]: https://lists.sr.ht/~sircmpwn/koushin | |||
[issue tracker]: https://todo.sr.ht/~sircmpwn/koushin | |||
[Go plugin helpers]: https://godoc.org/git.sr.ht/~emersion/alps#GoPlugin | |||
[mailing list]: https://lists.sr.ht/~sircmpwn/alps | |||
[issue tracker]: https://todo.sr.ht/~sircmpwn/alps |
@@ -7,28 +7,28 @@ import ( | |||
"os/signal" | |||
"syscall" | |||
"git.sr.ht/~emersion/koushin" | |||
"git.sr.ht/~emersion/alps" | |||
"github.com/labstack/echo/v4" | |||
"github.com/labstack/echo/v4/middleware" | |||
"github.com/labstack/gommon/log" | |||
_ "git.sr.ht/~emersion/koushin/plugins/base" | |||
_ "git.sr.ht/~emersion/koushin/plugins/caldav" | |||
_ "git.sr.ht/~emersion/koushin/plugins/carddav" | |||
_ "git.sr.ht/~emersion/koushin/plugins/lua" | |||
_ "git.sr.ht/~emersion/koushin/plugins/viewhtml" | |||
_ "git.sr.ht/~emersion/koushin/plugins/viewtext" | |||
_ "git.sr.ht/~emersion/alps/plugins/base" | |||
_ "git.sr.ht/~emersion/alps/plugins/caldav" | |||
_ "git.sr.ht/~emersion/alps/plugins/carddav" | |||
_ "git.sr.ht/~emersion/alps/plugins/lua" | |||
_ "git.sr.ht/~emersion/alps/plugins/viewhtml" | |||
_ "git.sr.ht/~emersion/alps/plugins/viewtext" | |||
) | |||
func main() { | |||
var options koushin.Options | |||
var options alps.Options | |||
var addr string | |||
flag.StringVar(&options.Theme, "theme", "", "default theme") | |||
flag.StringVar(&addr, "addr", ":1323", "listening address") | |||
flag.BoolVar(&options.Debug, "debug", false, "enable debug logs") | |||
flag.Usage = func() { | |||
fmt.Fprintf(flag.CommandLine.Output(), "usage: koushin [options...] <upstream servers...>\n") | |||
fmt.Fprintf(flag.CommandLine.Output(), "usage: alps [options...] <upstream servers...>\n") | |||
flag.PrintDefaults() | |||
} | |||
@@ -45,7 +45,7 @@ func main() { | |||
if l, ok := e.Logger.(*log.Logger); ok { | |||
l.SetHeader("${time_rfc3339} ${level}") | |||
} | |||
s, err := koushin.New(e, &options) | |||
s, err := alps.New(e, &options) | |||
if err != nil { | |||
e.Logger.Fatal(err) | |||
} |
@@ -1,6 +1,6 @@ | |||
#!/bin/sh | |||
# Watch themes and plugins files, automatically reload koushin on change. | |||
# Watch themes and plugins files, automatically reload alps on change. | |||
events=modify,create,delete,move | |||
targets="themes/ plugins/" | |||
@@ -8,6 +8,6 @@ targets="themes/ plugins/" | |||
inotifywait -e "$events" -m -r $targets | while read line; do | |||
jobs >/dev/null # Reap status of any terminated job | |||
if [ -z "$(jobs)" ]; then | |||
(sleep 0.5 && pkill -USR1 koushin) & | |||
(sleep 0.5 && pkill -USR1 alps) & | |||
fi | |||
done |
@@ -1,4 +1,4 @@ | |||
package koushin | |||
package alps | |||
import ( | |||
"fmt" | |||
@@ -1,22 +1,22 @@ | |||
# SYNOPSIS | |||
koushin [options...] <upstream servers...> | |||
alps [options...] <upstream servers...> | |||
# DESCRIPTION | |||
koushin is a simple and extensible webmail. It offers a web interface for IMAP, | |||
alps is a simple and extensible webmail. It offers a web interface for IMAP, | |||
SMTP and other upstream servers. | |||
At least one upstream IMAP server needs to be specified. The easiest way to do | |||
so is to just specify a domain name: | |||
koushin example.org | |||
alps example.org | |||
This assumes SRV DNS records are properly set up (see [RFC 6186]). | |||
Alternatively, one or more upstream server URLs can be specified: | |||
koushin imaps://mail.example.org:993 smtps://mail.example.org:465 | |||
alps imaps://mail.example.org:993 smtps://mail.example.org:465 | |||
The following URL schemes are supported: | |||
@@ -1,22 +1,22 @@ | |||
// Package exampleplugin is an example Go plugin for koushin. | |||
// Package exampleplugin is an example Go plugin for alps. | |||
// | |||
// To enable it, import this package from cmd/koushin/main.go. | |||
// To enable it, import this package from cmd/alps/main.go. | |||
package exampleplugin | |||
import ( | |||
"fmt" | |||
"net/http" | |||
"git.sr.ht/~emersion/koushin" | |||
koushinbase "git.sr.ht/~emersion/koushin/plugins/base" | |||
"git.sr.ht/~emersion/alps" | |||
alpsbase "git.sr.ht/~emersion/alps/plugins/base" | |||
) | |||
func init() { | |||
p := koushin.GoPlugin{Name: "example"} | |||
p := alps.GoPlugin{Name: "example"} | |||
// Setup a function called when the mailbox view is rendered | |||
p.Inject("mailbox.html", func(ctx *koushin.Context, kdata koushin.RenderData) error { | |||
data := kdata.(*koushinbase.MailboxRenderData) | |||
p.Inject("mailbox.html", func(ctx *alps.Context, kdata alps.RenderData) error { | |||
data := kdata.(*alpsbase.MailboxRenderData) | |||
fmt.Println("The mailbox view for " + data.Mailbox.Name + " is being rendered") | |||
// Set extra data that can be accessed from the mailbox.html template | |||
data.Extra["Example"] = "Hi from Go" | |||
@@ -24,7 +24,7 @@ func init() { | |||
}) | |||
// Wire up a new route | |||
p.GET("/example", func(ctx *koushin.Context) error { | |||
p.GET("/example", func(ctx *alps.Context) error { | |||
return ctx.String(http.StatusOK, "This is an example page.") | |||
}) | |||
@@ -35,5 +35,5 @@ func init() { | |||
}, | |||
}) | |||
koushin.RegisterPluginLoader(p.Loader()) | |||
alps.RegisterPluginLoader(p.Loader()) | |||
} |
@@ -2,18 +2,18 @@ | |||
print("Hi, this is an example Lua plugin") | |||
-- Setup a function called when the mailbox view is rendered | |||
koushin.on_render("mailbox.html", function(data) | |||
alps.on_render("mailbox.html", function(data) | |||
print("The mailbox view for " .. data.Mailbox.Name .. " is being rendered") | |||
-- Set extra data that can be accessed from the mailbox.html template | |||
data.Extra.Example = "Hi from Lua" | |||
end) | |||
-- Wire up a new route | |||
koushin.set_route("GET", "/example", function(ctx) | |||
alps.set_route("GET", "/example", function(ctx) | |||
ctx:String(200, "This is an example page.") | |||
end) | |||
-- Set a filter function that can be used from templates | |||
koushin.set_filter("example_and", function(a, b) | |||
alps.set_filter("example_and", function(a, b) | |||
return a .. " and " .. b | |||
end) |
@@ -1,21 +1,21 @@ | |||
# Running koushin with a Google account | |||
# Running alps with a Google account | |||
## Create an application password | |||
First, you'll need to obtain an application-specific password for koushin from | |||
First, you'll need to obtain an application-specific password for alps from | |||
the [app passwords] page on your Google account. | |||
## Run koushin | |||
## Run alps | |||
Start koushin with these upstream URLs: | |||
Start alps with these upstream URLs: | |||
koushin imaps://imap.gmail.com smtps://smtp.gmail.com \ | |||
alps imaps://imap.gmail.com smtps://smtp.gmail.com \ | |||
carddavs://www.googleapis.com/carddav/v1/principals/YOUREMAIL/ \ | |||
caldavs://www.google.com/calendar/dav | |||
Replace `YOUREMAIL` with your Google account's e-mail address. | |||
Once koushin is started, you can login with your e-mail address and the app | |||
Once alps is started, you can login with your e-mail address and the app | |||
password. | |||
[app passwords]: https://security.google.com/settings/security/apppasswords |
@@ -17,7 +17,7 @@ Assets in `plugins/<name>/public/assets/*` are served by the HTTP server at | |||
## Go plugins | |||
They can use the [Go plugin helpers] and need to be included at compile-time in | |||
`cmd/koushin/main.go`. | |||
`cmd/alps/main.go`. | |||
## Lua plugins | |||
@@ -25,8 +25,8 @@ The entry point is at `plugins/<name>/main.lua`. | |||
API: | |||
* `koushin.on_render(name, f)`: prior to rendering the template `name`, call | |||
* `alps.on_render(name, f)`: prior to rendering the template `name`, call | |||
`f` with the template data (the special name `*` matches all templates) | |||
* `koushin.set_filter(name, f)`: set a template function | |||
* `koushin.set_route(method, path, f)`: register a new HTTP route, `f` will be | |||
* `alps.set_filter(name, f)`: set a template function | |||
* `alps.set_route(method, path, f)`: register a new HTTP route, `f` will be | |||
called with the HTTP context |
@@ -1,4 +1,4 @@ | |||
module git.sr.ht/~emersion/koushin | |||
module git.sr.ht/~emersion/alps | |||
go 1.13 | |||
@@ -1,4 +1,4 @@ | |||
package koushin | |||
package alps | |||
import ( | |||
"fmt" | |||
@@ -1,4 +1,4 @@ | |||
package koushin | |||
package alps | |||
import ( | |||
"html/template" | |||
@@ -9,7 +9,7 @@ import ( | |||
// PluginDir is the path to the plugins directory. | |||
const PluginDir = "plugins" | |||
// Plugin extends koushin with additional functionality. | |||
// Plugin extends alps with additional functionality. | |||
type Plugin interface { | |||
// Name should return the plugin name. | |||
Name() string | |||
@@ -1,4 +1,4 @@ | |||
package koushin | |||
package alps | |||
import ( | |||
"html/template" | |||
@@ -71,7 +71,7 @@ type goPluginRoute struct { | |||
// | |||
// p := GoPlugin{Name: "my-plugin"} | |||
// // Define routes, template functions, etc | |||
// koushin.RegisterPluginLoader(p.Loader()) | |||
// alps.RegisterPluginLoader(p.Loader()) | |||
type GoPlugin struct { | |||
Name string | |||
@@ -1,4 +1,4 @@ | |||
package koushinbase | |||
package alpsbase | |||
import ( | |||
"bufio" | |||
@@ -1,14 +1,14 @@ | |||
package koushinbase | |||
package alpsbase | |||
import ( | |||
"git.sr.ht/~emersion/koushin" | |||
"git.sr.ht/~emersion/alps" | |||
) | |||
func init() { | |||
p := koushin.GoPlugin{Name: "base"} | |||
p := alps.GoPlugin{Name: "base"} | |||
p.TemplateFuncs(templateFuncs) | |||
registerRoutes(&p) | |||
koushin.RegisterPluginLoader(p.Loader()) | |||
alps.RegisterPluginLoader(p.Loader()) | |||
} |
@@ -1,6 +1,6 @@ | |||
{{template "head.html"}} | |||
<h1>koushin</h1> | |||
<h1>alps</h1> | |||
<p> | |||
<a href="/mailbox/INBOX">Back</a> | |||
@@ -2,6 +2,6 @@ | |||
<html lang="en"> | |||
<head> | |||
<meta charset="utf-8"> | |||
<title>koushin</title> | |||
<title>alps</title> | |||
</head> | |||
<body> |
@@ -1,6 +1,6 @@ | |||
{{template "head.html"}} | |||
<h1>koushin</h1> | |||
<h1>alps</h1> | |||
<form method="post" action=""> | |||
<label for="username">Username:</label> | |||
@@ -1,6 +1,6 @@ | |||
{{template "head.html"}} | |||
<h1>koushin</h1> | |||
<h1>alps</h1> | |||
<p> | |||
<a href="/logout">Logout</a> | |||
@@ -1,6 +1,6 @@ | |||
{{template "head.html"}} | |||
<h1>koushin</h1> | |||
<h1>alps</h1> | |||
<p> | |||
<a href="/mailbox/{{.Mailbox.Name | pathescape}}?page={{.MailboxPage}}"> | |||
@@ -1,6 +1,6 @@ | |||
{{template "head.html"}} | |||
<h1>koushin</h1> | |||
<h1>alps</h1> | |||
<p> | |||
<a href="/mailbox/INBOX">Back</a> | |||
@@ -1,4 +1,4 @@ | |||
package koushinbase | |||
package alpsbase | |||
import ( | |||
"bytes" | |||
@@ -11,7 +11,7 @@ import ( | |||
"strconv" | |||
"strings" | |||
"git.sr.ht/~emersion/koushin" | |||
"git.sr.ht/~emersion/alps" | |||
"github.com/emersion/go-imap" | |||
imapmove "github.com/emersion/go-imap-move" | |||
imapclient "github.com/emersion/go-imap/client" | |||
@@ -21,18 +21,18 @@ import ( | |||
"github.com/labstack/echo/v4" | |||
) | |||
func registerRoutes(p *koushin.GoPlugin) { | |||
p.GET("/", func(ctx *koushin.Context) error { | |||
func registerRoutes(p *alps.GoPlugin) { | |||
p.GET("/", func(ctx *alps.Context) error { | |||
return ctx.Redirect(http.StatusFound, "/mailbox/INBOX") | |||
}) | |||
p.GET("/mailbox/:mbox", handleGetMailbox) | |||
p.POST("/mailbox/:mbox", handleGetMailbox) | |||
p.GET("/message/:mbox/:uid", func(ctx *koushin.Context) error { | |||
p.GET("/message/:mbox/:uid", func(ctx *alps.Context) error { | |||
return handleGetPart(ctx, false) | |||
}) | |||
p.GET("/message/:mbox/:uid/raw", func(ctx *koushin.Context) error { | |||
p.GET("/message/:mbox/:uid/raw", func(ctx *alps.Context) error { | |||
return handleGetPart(ctx, true) | |||
}) | |||
@@ -64,7 +64,7 @@ func registerRoutes(p *koushin.GoPlugin) { | |||
} | |||
type MailboxRenderData struct { | |||
koushin.BaseRenderData | |||
alps.BaseRenderData | |||
Mailbox *MailboxStatus | |||
Mailboxes []MailboxInfo | |||
Messages []IMAPMessage | |||
@@ -72,7 +72,7 @@ type MailboxRenderData struct { | |||
Query string | |||
} | |||
func handleGetMailbox(ctx *koushin.Context) error { | |||
func handleGetMailbox(ctx *alps.Context) error { | |||
mboxName, err := url.PathUnescape(ctx.Param("mbox")) | |||
if err != nil { | |||
return echo.NewHTTPError(http.StatusBadRequest, err) | |||
@@ -136,7 +136,7 @@ func handleGetMailbox(ctx *koushin.Context) error { | |||
} | |||
return ctx.Render(http.StatusOK, "mailbox.html", &MailboxRenderData{ | |||
BaseRenderData: *koushin.NewBaseRenderData(ctx), | |||
BaseRenderData: *alps.NewBaseRenderData(ctx), | |||
Mailbox: mbox, | |||
Mailboxes: mailboxes, | |||
Messages: msgs, | |||
@@ -146,13 +146,13 @@ func handleGetMailbox(ctx *koushin.Context) error { | |||
}) | |||
} | |||
func handleLogin(ctx *koushin.Context) error { | |||
func handleLogin(ctx *alps.Context) error { | |||
username := ctx.FormValue("username") | |||
password := ctx.FormValue("password") | |||
if username != "" && password != "" { | |||
s, err := ctx.Server.Sessions.Put(username, password) | |||
if err != nil { | |||
if _, ok := err.(koushin.AuthError); ok { | |||
if _, ok := err.(alps.AuthError); ok { | |||
return ctx.Render(http.StatusOK, "login.html", nil) | |||
} | |||
return fmt.Errorf("failed to put connection in pool: %v", err) | |||
@@ -165,17 +165,17 @@ func handleLogin(ctx *koushin.Context) error { | |||
return ctx.Redirect(http.StatusFound, "/mailbox/INBOX") | |||
} | |||
return ctx.Render(http.StatusOK, "login.html", koushin.NewBaseRenderData(ctx)) | |||
return ctx.Render(http.StatusOK, "login.html", alps.NewBaseRenderData(ctx)) | |||
} | |||
func handleLogout(ctx *koushin.Context) error { | |||
func handleLogout(ctx *alps.Context) error { | |||
ctx.Session.Close() | |||
ctx.SetSession(nil) | |||
return ctx.Redirect(http.StatusFound, "/login") | |||
} | |||
type MessageRenderData struct { | |||
koushin.BaseRenderData | |||
alps.BaseRenderData | |||
Mailboxes []MailboxInfo | |||
Mailbox *MailboxStatus | |||
Message *IMAPMessage | |||
@@ -185,7 +185,7 @@ type MessageRenderData struct { | |||
Flags map[string]bool | |||
} | |||
func handleGetPart(ctx *koushin.Context, raw bool) error { | |||
func handleGetPart(ctx *alps.Context, raw bool) error { | |||
mboxName, uid, err := parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid")) | |||
if err != nil { | |||
return echo.NewHTTPError(http.StatusBadRequest, err) | |||
@@ -271,7 +271,7 @@ func handleGetPart(ctx *koushin.Context, raw bool) error { | |||
} | |||
return ctx.Render(http.StatusOK, "message.html", &MessageRenderData{ | |||
BaseRenderData: *koushin.NewBaseRenderData(ctx), | |||
BaseRenderData: *alps.NewBaseRenderData(ctx), | |||
Mailboxes: mailboxes, | |||
Mailbox: mbox, | |||
Message: msg, | |||
@@ -283,7 +283,7 @@ func handleGetPart(ctx *koushin.Context, raw bool) error { | |||
} | |||
type ComposeRenderData struct { | |||
koushin.BaseRenderData | |||
alps.BaseRenderData | |||
Message *OutgoingMessage | |||
} | |||
@@ -300,12 +300,12 @@ type composeOptions struct { | |||
// Send message, append it to the Sent mailbox, mark the original message as | |||
// answered | |||
func submitCompose(ctx *koushin.Context, msg *OutgoingMessage, options *composeOptions) error { | |||
func submitCompose(ctx *alps.Context, msg *OutgoingMessage, options *composeOptions) error { | |||
err := ctx.Session.DoSMTP(func(c *smtp.Client) error { | |||
return sendMessage(c, msg) | |||
}) | |||
if err != nil { | |||
if _, ok := err.(koushin.AuthError); ok { | |||
if _, ok := err.(alps.AuthError); ok { | |||
return echo.NewHTTPError(http.StatusForbidden, err) | |||
} | |||
return fmt.Errorf("failed to send message: %v", err) | |||
@@ -338,7 +338,7 @@ func submitCompose(ctx *koushin.Context, msg *OutgoingMessage, options *composeO | |||
return ctx.Redirect(http.StatusFound, "/mailbox/INBOX") | |||
} | |||
func handleCompose(ctx *koushin.Context, msg *OutgoingMessage, options *composeOptions) error { | |||
func handleCompose(ctx *alps.Context, msg *OutgoingMessage, options *composeOptions) error { | |||
if msg.From == "" && strings.ContainsRune(ctx.Session.Username(), '@') { | |||
msg.From = ctx.Session.Username() | |||
} | |||
@@ -438,12 +438,12 @@ func handleCompose(ctx *koushin.Context, msg *OutgoingMessage, options *composeO | |||
} | |||
return ctx.Render(http.StatusOK, "compose.html", &ComposeRenderData{ | |||
BaseRenderData: *koushin.NewBaseRenderData(ctx), | |||
BaseRenderData: *alps.NewBaseRenderData(ctx), | |||
Message: msg, | |||
}) | |||
} | |||
func handleComposeNew(ctx *koushin.Context) error { | |||
func handleComposeNew(ctx *alps.Context) error { | |||
// These are common mailto URL query parameters | |||
// TODO: cc, bcc | |||
return handleCompose(ctx, &OutgoingMessage{ | |||
@@ -462,7 +462,7 @@ func unwrapIMAPAddressList(addrs []*imap.Address) []string { | |||
return l | |||
} | |||
func handleReply(ctx *koushin.Context) error { | |||
func handleReply(ctx *alps.Context) error { | |||
var inReplyToPath messagePath | |||
var err error | |||
inReplyToPath.Mailbox, inReplyToPath.Uid, err = parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid")) | |||
@@ -521,7 +521,7 @@ func handleReply(ctx *koushin.Context) error { | |||
return handleCompose(ctx, &msg, &composeOptions{InReplyTo: &inReplyToPath}) | |||
} | |||
func handleForward(ctx *koushin.Context) error { | |||
func handleForward(ctx *alps.Context) error { | |||
var sourcePath messagePath | |||
var err error | |||
sourcePath.Mailbox, sourcePath.Uid, err = parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid")) | |||
@@ -585,7 +585,7 @@ func handleForward(ctx *koushin.Context) error { | |||
return handleCompose(ctx, &msg, &composeOptions{Forward: &sourcePath}) | |||
} | |||
func handleEdit(ctx *koushin.Context) error { | |||
func handleEdit(ctx *alps.Context) error { | |||
var sourcePath messagePath | |||
var err error | |||
sourcePath.Mailbox, sourcePath.Uid, err = parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid")) | |||
@@ -653,14 +653,14 @@ func handleEdit(ctx *koushin.Context) error { | |||
return handleCompose(ctx, &msg, &composeOptions{Draft: &sourcePath}) | |||
} | |||
func formOrQueryParam(ctx *koushin.Context, k string) string { | |||
func formOrQueryParam(ctx *alps.Context, k string) string { | |||
if v := ctx.FormValue(k); v != "" { | |||
return v | |||
} | |||
return ctx.QueryParam(k) | |||
} | |||
func handleMove(ctx *koushin.Context) error { | |||
func handleMove(ctx *alps.Context) error { | |||
mboxName, err := url.PathUnescape(ctx.Param("mbox")) | |||
if err != nil { | |||
return echo.NewHTTPError(http.StatusBadRequest, err) | |||
@@ -706,7 +706,7 @@ func handleMove(ctx *koushin.Context) error { | |||
return ctx.Redirect(http.StatusFound, fmt.Sprintf("/mailbox/%v", url.PathEscape(to))) | |||
} | |||
func handleDelete(ctx *koushin.Context) error { | |||
func handleDelete(ctx *alps.Context) error { | |||
mboxName, err := url.PathUnescape(ctx.Param("mbox")) | |||
if err != nil { | |||
return echo.NewHTTPError(http.StatusBadRequest, err) | |||
@@ -757,7 +757,7 @@ func handleDelete(ctx *koushin.Context) error { | |||
return ctx.Redirect(http.StatusFound, fmt.Sprintf("/mailbox/%v", url.PathEscape(mboxName))) | |||
} | |||
func handleSetFlags(ctx *koushin.Context) error { | |||
func handleSetFlags(ctx *alps.Context) error { | |||
mboxName, err := url.PathUnescape(ctx.Param("mbox")) | |||
if err != nil { | |||
return echo.NewHTTPError(http.StatusBadRequest, err) | |||
@@ -840,11 +840,11 @@ type Settings struct { | |||
MessagesPerPage int | |||
} | |||
func loadSettings(s koushin.Store) (*Settings, error) { | |||
func loadSettings(s alps.Store) (*Settings, error) { | |||
settings := &Settings{ | |||
MessagesPerPage: 50, | |||
} | |||
if err := s.Get(settingsKey, settings); err != nil && err != koushin.ErrNoStoreEntry { | |||
if err := s.Get(settingsKey, settings); err != nil && err != alps.ErrNoStoreEntry { | |||
return nil, err | |||
} | |||
if err := settings.check(); err != nil { | |||
@@ -861,11 +861,11 @@ func (s *Settings) check() error { | |||
} | |||
type SettingsRenderData struct { | |||
koushin.BaseRenderData | |||
alps.BaseRenderData | |||
Settings *Settings | |||
} | |||
func handleSettings(ctx *koushin.Context) error { | |||
func handleSettings(ctx *alps.Context) error { | |||
settings, err := loadSettings(ctx.Session.Store()) | |||
if err != nil { | |||
return fmt.Errorf("failed to load settings: %v", err) | |||
@@ -888,7 +888,7 @@ func handleSettings(ctx *koushin.Context) error { | |||
} | |||
return ctx.Render(http.StatusOK, "settings.html", &SettingsRenderData{ | |||
BaseRenderData: *koushin.NewBaseRenderData(ctx), | |||
BaseRenderData: *alps.NewBaseRenderData(ctx), | |||
Settings: settings, | |||
}) | |||
} |
@@ -1,4 +1,4 @@ | |||
package koushinbase | |||
package alpsbase | |||
import ( | |||
"bufio" | |||
@@ -1,4 +1,4 @@ | |||
package koushinbase | |||
package alpsbase | |||
import ( | |||
"fmt" | |||
@@ -1,4 +1,4 @@ | |||
package koushinbase | |||
package alpsbase | |||
import ( | |||
"html/template" | |||
@@ -1,9 +1,9 @@ | |||
package koushinbase | |||
package alpsbase | |||
import ( | |||
"fmt" | |||
"git.sr.ht/~emersion/koushin" | |||
"git.sr.ht/~emersion/alps" | |||
"github.com/emersion/go-message" | |||
) | |||
@@ -16,7 +16,7 @@ type Viewer interface { | |||
// ViewMessagePart renders a message part. The returned value is displayed | |||
// in a template. ErrViewUnsupported is returned if the message part isn't | |||
// supported. | |||
ViewMessagePart(*koushin.Context, *IMAPMessage, *message.Entity) (interface{}, error) | |||
ViewMessagePart(*alps.Context, *IMAPMessage, *message.Entity) (interface{}, error) | |||
} | |||
var viewers []Viewer | |||
@@ -26,7 +26,7 @@ func RegisterViewer(viewer Viewer) { | |||
viewers = append(viewers, viewer) | |||
} | |||
func viewMessagePart(ctx *koushin.Context, msg *IMAPMessage, part *message.Entity) (interface{}, error) { | |||
func viewMessagePart(ctx *alps.Context, msg *IMAPMessage, part *message.Entity) (interface{}, error) { | |||
for _, viewer := range viewers { | |||
v, err := viewer.ViewMessagePart(ctx, msg, part) | |||
if err == ErrViewUnsupported { | |||
@@ -1,11 +1,11 @@ | |||
package koushincaldav | |||
package alpscaldav | |||
import ( | |||
"fmt" | |||
"net/http" | |||
"net/url" | |||
"git.sr.ht/~emersion/koushin" | |||
"git.sr.ht/~emersion/alps" | |||
"github.com/emersion/go-webdav/caldav" | |||
) | |||
@@ -13,7 +13,7 @@ var errNoCalendar = fmt.Errorf("caldav: no calendar found") | |||
type authRoundTripper struct { | |||
upstream http.RoundTripper | |||
session *koushin.Session | |||
session *alps.Session | |||
} | |||
func (rt *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { | |||
@@ -21,7 +21,7 @@ func (rt *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) | |||
return rt.upstream.RoundTrip(req) | |||
} | |||
func newClient(u *url.URL, session *koushin.Session) (*caldav.Client, error) { | |||
func newClient(u *url.URL, session *alps.Session) (*caldav.Client, error) { | |||
rt := authRoundTripper{ | |||
upstream: http.DefaultTransport, | |||
session: session, | |||
@@ -34,7 +34,7 @@ func newClient(u *url.URL, session *koushin.Session) (*caldav.Client, error) { | |||
return c, nil | |||
} | |||
func getCalendar(u *url.URL, session *koushin.Session) (*caldav.Client, *caldav.Calendar, error) { | |||
func getCalendar(u *url.URL, session *alps.Session) (*caldav.Client, *caldav.Calendar, error) { | |||
c, err := newClient(u, session) | |||
if err != nil { | |||
return nil, nil, err | |||
@@ -1,11 +1,11 @@ | |||
package koushincaldav | |||
package alpscaldav | |||
import ( | |||
"fmt" | |||
"net/http" | |||
"net/url" | |||
"git.sr.ht/~emersion/koushin" | |||
"git.sr.ht/~emersion/alps" | |||
) | |||
func sanityCheckURL(u *url.URL) error { | |||
@@ -27,9 +27,9 @@ func sanityCheckURL(u *url.URL) error { | |||
return nil | |||
} | |||
func newPlugin(srv *koushin.Server) (koushin.Plugin, error) { | |||
func newPlugin(srv *alps.Server) (alps.Plugin, error) { | |||
u, err := srv.Upstream("caldavs", "caldav+insecure", "https", "http+insecure") | |||
if _, ok := err.(*koushin.NoUpstreamError); ok { | |||
if _, ok := err.(*alps.NoUpstreamError); ok { | |||
return nil, nil | |||
} else if err != nil { | |||
return nil, fmt.Errorf("caldav: failed to parse upstream caldav server: %v", err) | |||
@@ -53,7 +53,7 @@ func newPlugin(srv *koushin.Server) (koushin.Plugin, error) { | |||
srv.Logger().Printf("Configured upstream CalDAV server: %v", u) | |||
p := koushin.GoPlugin{Name: "caldav"} | |||
p := alps.GoPlugin{Name: "caldav"} | |||
registerRoutes(&p, u) | |||
@@ -61,7 +61,7 @@ func newPlugin(srv *koushin.Server) (koushin.Plugin, error) { | |||
} | |||
func init() { | |||
koushin.RegisterPluginLoader(func(s *koushin.Server) ([]koushin.Plugin, error) { | |||
alps.RegisterPluginLoader(func(s *alps.Server) ([]alps.Plugin, error) { | |||
p, err := newPlugin(s) | |||
if err != nil { | |||
return nil, err | |||
@@ -69,6 +69,6 @@ func init() { | |||
if p == nil { | |||
return nil, nil | |||
} | |||
return []koushin.Plugin{p}, err | |||
return []alps.Plugin{p}, err | |||
}) | |||
} |
@@ -1,6 +1,6 @@ | |||
{{template "head.html"}} | |||
<h1>koushin</h1> | |||
<h1>alps</h1> | |||
<p> | |||
<a href="/">Back</a> | |||
@@ -1,6 +1,6 @@ | |||
{{template "head.html"}} | |||
<h1>koushin</h1> | |||
<h1>alps</h1> | |||
<p> | |||
<a href="/calendar">Back</a> | |||
@@ -1,4 +1,4 @@ | |||
package koushincaldav | |||
package alpscaldav | |||
import ( | |||
"fmt" | |||
@@ -6,12 +6,12 @@ import ( | |||
"net/url" | |||
"time" | |||
"git.sr.ht/~emersion/koushin" | |||
"git.sr.ht/~emersion/alps" | |||
"github.com/emersion/go-webdav/caldav" | |||
) | |||
type CalendarRenderData struct { | |||
koushin.BaseRenderData | |||
alps.BaseRenderData | |||
Time time.Time | |||
Calendar *caldav.Calendar | |||
Events []caldav.CalendarObject | |||
@@ -19,15 +19,15 @@ type CalendarRenderData struct { | |||
} | |||
type EventRenderData struct { | |||
koushin.BaseRenderData | |||
alps.BaseRenderData | |||
Calendar *caldav.Calendar | |||
Event *caldav.CalendarObject | |||
} | |||
var monthPageLayout = "2006-01" | |||
func registerRoutes(p *koushin.GoPlugin, u *url.URL) { | |||
p.GET("/calendar", func(ctx *koushin.Context) error { | |||
func registerRoutes(p *alps.GoPlugin, u *url.URL) { | |||
p.GET("/calendar", func(ctx *alps.Context) error { | |||
var start time.Time | |||
if s := ctx.QueryParam("month"); s != "" { | |||
var err error | |||
@@ -77,7 +77,7 @@ func registerRoutes(p *koushin.GoPlugin, u *url.URL) { | |||
} | |||
return ctx.Render(http.StatusOK, "calendar.html", &CalendarRenderData{ | |||
BaseRenderData: *koushin.NewBaseRenderData(ctx), | |||
BaseRenderData: *alps.NewBaseRenderData(ctx), | |||
Time: start, | |||
Calendar: calendar, | |||
Events: events, | |||
@@ -86,7 +86,7 @@ func registerRoutes(p *koushin.GoPlugin, u *url.URL) { | |||
}) | |||
}) | |||
p.GET("/calendar/:uid", func(ctx *koushin.Context) error { | |||
p.GET("/calendar/:uid", func(ctx *alps.Context) error { | |||
uid := ctx.Param("uid") | |||
c, calendar, err := getCalendar(u, ctx.Session) | |||
@@ -131,7 +131,7 @@ func registerRoutes(p *koushin.GoPlugin, u *url.URL) { | |||
event := &events[0] | |||
return ctx.Render(http.StatusOK, "event.html", &EventRenderData{ | |||
BaseRenderData: *koushin.NewBaseRenderData(ctx), | |||
BaseRenderData: *alps.NewBaseRenderData(ctx), | |||
Calendar: calendar, | |||
Event: event, | |||
}) | |||
@@ -1,11 +1,11 @@ | |||
package koushincarddav | |||
package alpscarddav | |||
import ( | |||
"fmt" | |||
"net/http" | |||
"net/url" | |||
"git.sr.ht/~emersion/koushin" | |||
"git.sr.ht/~emersion/alps" | |||
"github.com/emersion/go-webdav/carddav" | |||
) | |||
@@ -13,7 +13,7 @@ var errNoAddressBook = fmt.Errorf("carddav: no address book found") | |||
type authRoundTripper struct { | |||
upstream http.RoundTripper | |||
session *koushin.Session | |||
session *alps.Session | |||
} | |||
func (rt *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { | |||
@@ -21,7 +21,7 @@ func (rt *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) | |||
return rt.upstream.RoundTrip(req) | |||
} | |||
func newClient(u *url.URL, session *koushin.Session) (*carddav.Client, error) { | |||
func newClient(u *url.URL, session *alps.Session) (*carddav.Client, error) { | |||
rt := authRoundTripper{ | |||
upstream: http.DefaultTransport, | |||
session: session, | |||
@@ -1,4 +1,4 @@ | |||
package koushincarddav | |||
package alpscarddav | |||
import ( | |||
"fmt" | |||
@@ -6,8 +6,8 @@ import ( | |||
"net/url" | |||
"strings" | |||
"git.sr.ht/~emersion/koushin" | |||
koushinbase "git.sr.ht/~emersion/koushin/plugins/base" | |||
"git.sr.ht/~emersion/alps" | |||
alpsbase "git.sr.ht/~emersion/alps/plugins/base" | |||
"github.com/emersion/go-vcard" | |||
"github.com/emersion/go-webdav/carddav" | |||
) | |||
@@ -32,16 +32,16 @@ func sanityCheckURL(u *url.URL) error { | |||
} | |||
type plugin struct { | |||
koushin.GoPlugin | |||
alps.GoPlugin | |||
url *url.URL | |||
homeSetCache map[string]string | |||
} | |||
func (p *plugin) client(session *koushin.Session) (*carddav.Client, error) { | |||
func (p *plugin) client(session *alps.Session) (*carddav.Client, error) { | |||
return newClient(p.url, session) | |||
} | |||
func (p *plugin) clientWithAddressBook(session *koushin.Session) (*carddav.Client, *carddav.AddressBook, error) { | |||
func (p *plugin) clientWithAddressBook(session *alps.Session) (*carddav.Client, *carddav.AddressBook, error) { | |||
c, err := newClient(p.url, session) | |||
if err != nil { | |||
return nil, nil, fmt.Errorf("failed to create CardDAV client: %v", err) | |||
@@ -73,9 +73,9 @@ func (p *plugin) clientWithAddressBook(session *koushin.Session) (*carddav.Clien | |||
return c, &addressBooks[0], nil | |||
} | |||
func newPlugin(srv *koushin.Server) (koushin.Plugin, error) { | |||
func newPlugin(srv *alps.Server) (alps.Plugin, error) { | |||
u, err := srv.Upstream("carddavs", "carddav+insecure", "https", "http+insecure") | |||
if _, ok := err.(*koushin.NoUpstreamError); ok { | |||
if _, ok := err.(*alps.NoUpstreamError); ok { | |||
return nil, nil | |||
} else if err != nil { | |||
return nil, fmt.Errorf("carddav: failed to parse upstream CardDAV server: %v", err) | |||
@@ -105,7 +105,7 @@ func newPlugin(srv *koushin.Server) (koushin.Plugin, error) { | |||
srv.Logger().Printf("Configured upstream CardDAV server: %v", u) | |||
p := &plugin{ | |||
GoPlugin: koushin.GoPlugin{Name: "carddav"}, | |||
GoPlugin: alps.GoPlugin{Name: "carddav"}, | |||
url: u, | |||
homeSetCache: make(map[string]string), | |||
} | |||
@@ -118,8 +118,8 @@ func newPlugin(srv *koushin.Server) (koushin.Plugin, error) { | |||
}, | |||
}) | |||
p.Inject("compose.html", func(ctx *koushin.Context, _data koushin.RenderData) error { | |||
data := _data.(*koushinbase.ComposeRenderData) | |||
p.Inject("compose.html", func(ctx *alps.Context, _data alps.RenderData) error { | |||
data := _data.(*alpsbase.ComposeRenderData) | |||
c, addressBook, err := p.clientWithAddressBook(ctx.Session) | |||
if err == errNoAddressBook { | |||
@@ -156,7 +156,7 @@ func newPlugin(srv *koushin.Server) (koushin.Plugin, error) { | |||
} | |||
func init() { | |||
koushin.RegisterPluginLoader(func(s *koushin.Server) ([]koushin.Plugin, error) { | |||
alps.RegisterPluginLoader(func(s *alps.Server) ([]alps.Plugin, error) { | |||
p, err := newPlugin(s) | |||
if err != nil { | |||
return nil, err | |||
@@ -164,6 +164,6 @@ func init() { | |||
if p == nil { | |||
return nil, nil | |||
} | |||
return []koushin.Plugin{p}, err | |||
return []alps.Plugin{p}, err | |||
}) | |||
} |
@@ -1,6 +1,6 @@ | |||
{{template "head.html"}} | |||
<h1>koushin</h1> | |||
<h1>alps</h1> | |||
<p> | |||
<a href="/">Back</a> · <a href="/contacts/create">Create new contact</a> | |||
@@ -1,6 +1,6 @@ | |||
{{template "head.html"}} | |||
<h1>koushin</h1> | |||
<h1>alps</h1> | |||
<p> | |||
<a href="/contacts">Back</a> | |||
@@ -1,6 +1,6 @@ | |||
{{template "head.html"}} | |||
<h1>koushin</h1> | |||
<h1>alps</h1> | |||
<p> | |||
<a href="/contacts">Back</a> | |||
@@ -1,4 +1,4 @@ | |||
package koushincarddav | |||
package alpscarddav | |||
import ( | |||
"fmt" | |||
@@ -7,7 +7,7 @@ import ( | |||
"path" | |||
"strings" | |||
"git.sr.ht/~emersion/koushin" | |||
"git.sr.ht/~emersion/alps" | |||
"github.com/emersion/go-vcard" | |||
"github.com/emersion/go-webdav/carddav" | |||
"github.com/google/uuid" | |||
@@ -15,19 +15,19 @@ import ( | |||
) | |||
type AddressBookRenderData struct { | |||
koushin.BaseRenderData | |||
alps.BaseRenderData | |||
AddressBook *carddav.AddressBook | |||
AddressObjects []AddressObject | |||
Query string | |||
} | |||
type AddressObjectRenderData struct { | |||
koushin.BaseRenderData | |||
alps.BaseRenderData | |||
AddressObject AddressObject | |||
} | |||
type UpdateAddressObjectRenderData struct { | |||
koushin.BaseRenderData | |||
alps.BaseRenderData | |||
AddressObject *carddav.AddressObject // nil if creating a new contact | |||
Card vcard.Card | |||
} | |||
@@ -42,7 +42,7 @@ func parseObjectPath(s string) (string, error) { | |||
} | |||
func registerRoutes(p *plugin) { | |||
p.GET("/contacts", func(ctx *koushin.Context) error { | |||
p.GET("/contacts", func(ctx *alps.Context) error { | |||
queryText := ctx.QueryParam("query") | |||
c, addressBook, err := p.clientWithAddressBook(ctx.Session) | |||
@@ -82,14 +82,14 @@ func registerRoutes(p *plugin) { | |||
} | |||
return ctx.Render(http.StatusOK, "address-book.html", &AddressBookRenderData{ | |||
BaseRenderData: *koushin.NewBaseRenderData(ctx), | |||
BaseRenderData: *alps.NewBaseRenderData(ctx), | |||
AddressBook: addressBook, | |||
AddressObjects: newAddressObjectList(aos), | |||
Query: queryText, | |||
}) | |||
}) | |||
p.GET("/contacts/:path", func(ctx *koushin.Context) error { | |||
p.GET("/contacts/:path", func(ctx *alps.Context) error { | |||
path, err := parseObjectPath(ctx.Param("path")) | |||
if err != nil { | |||
return err | |||
@@ -119,12 +119,12 @@ func registerRoutes(p *plugin) { | |||
ao := &aos[0] | |||
return ctx.Render(http.StatusOK, "address-object.html", &AddressObjectRenderData{ | |||
BaseRenderData: *koushin.NewBaseRenderData(ctx), | |||
BaseRenderData: *alps.NewBaseRenderData(ctx), | |||
AddressObject: AddressObject{ao}, | |||
}) | |||
}) | |||
updateContact := func(ctx *koushin.Context) error { | |||
updateContact := func(ctx *alps.Context) error { | |||
addressObjectPath, err := parseObjectPath(ctx.Param("path")) | |||
if err != nil { | |||
return err | |||
@@ -200,7 +200,7 @@ func registerRoutes(p *plugin) { | |||
} | |||
return ctx.Render(http.StatusOK, "update-address-object.html", &UpdateAddressObjectRenderData{ | |||
BaseRenderData: *koushin.NewBaseRenderData(ctx), | |||
BaseRenderData: *alps.NewBaseRenderData(ctx), | |||
AddressObject: ao, | |||
Card: card, | |||
}) | |||
@@ -1,11 +1,11 @@ | |||
package koushinlua | |||
package alpslua | |||
import ( | |||
"fmt" | |||
"html/template" | |||
"path/filepath" | |||
"git.sr.ht/~emersion/koushin" | |||
"git.sr.ht/~emersion/alps" | |||
"github.com/labstack/echo/v4" | |||
"github.com/yuin/gopher-lua" | |||
"layeh.com/gopher-luar" | |||
@@ -69,7 +69,7 @@ func (p *luaPlugin) setRoute(l *lua.LState) int { | |||
return 0 | |||
} | |||
func (p *luaPlugin) inject(name string, data koushin.RenderData) error { | |||
func (p *luaPlugin) inject(name string, data alps.RenderData) error { | |||
f, ok := p.renderCallbacks[name] | |||
if !ok { | |||
return nil | |||
@@ -87,7 +87,7 @@ func (p *luaPlugin) inject(name string, data koushin.RenderData) error { | |||
return nil | |||
} | |||
func (p *luaPlugin) Inject(ctx *koushin.Context, name string, data koushin.RenderData) error { | |||
func (p *luaPlugin) Inject(ctx *alps.Context, name string, data alps.RenderData) error { | |||
if err := p.inject("*", data); err != nil { | |||
return err | |||
} | |||
@@ -144,8 +144,8 @@ func loadLuaPlugin(filename string) (*luaPlugin, error) { | |||
filters: make(template.FuncMap), | |||
} | |||
mt := l.NewTypeMetatable("koushin") | |||
l.SetGlobal("koushin", mt) | |||
mt := l.NewTypeMetatable("alps") | |||
l.SetGlobal("alps", mt) | |||
l.SetField(mt, "on_render", l.NewFunction(p.onRender)) | |||
l.SetField(mt, "set_filter", l.NewFunction(p.setFilter)) | |||
l.SetField(mt, "set_route", l.NewFunction(p.setRoute)) | |||
@@ -158,15 +158,15 @@ func loadLuaPlugin(filename string) (*luaPlugin, error) { | |||
return p, nil | |||
} | |||
func loadAllLuaPlugins(s *koushin.Server) ([]koushin.Plugin, error) { | |||
func loadAllLuaPlugins(s *alps.Server) ([]alps.Plugin, error) { | |||
log := s.Logger() | |||
filenames, err := filepath.Glob(koushin.PluginDir + "/*/main.lua") | |||
filenames, err := filepath.Glob(alps.PluginDir + "/*/main.lua") | |||
if err != nil { | |||
return nil, fmt.Errorf("filepath.Glob failed: %v", err) | |||
} | |||
plugins := make([]koushin.Plugin, 0, len(filenames)) | |||
plugins := make([]alps.Plugin, 0, len(filenames)) | |||
for _, filename := range filenames { | |||
log.Printf("Loading Lua plugin %q", filename) | |||
@@ -1,9 +1,9 @@ | |||
package koushinlua | |||
package alpslua | |||
import ( | |||
"git.sr.ht/~emersion/koushin" | |||
"git.sr.ht/~emersion/alps" | |||
) | |||
func init() { | |||
koushin.RegisterPluginLoader(loadAllLuaPlugins) | |||
alps.RegisterPluginLoader(loadAllLuaPlugins) | |||
} |
@@ -1,4 +1,4 @@ | |||
package koushinviewhtml | |||
package alpsviewhtml | |||
import ( | |||
"io" | |||
@@ -8,8 +8,8 @@ import ( | |||
"strconv" | |||
"strings" | |||
"git.sr.ht/~emersion/koushin" | |||
koushinbase "git.sr.ht/~emersion/koushin/plugins/base" | |||
"git.sr.ht/~emersion/alps" | |||
alpsbase "git.sr.ht/~emersion/alps/plugins/base" | |||
"github.com/labstack/echo/v4" | |||
) | |||
@@ -19,10 +19,10 @@ var ( | |||
) | |||
func init() { | |||
p := koushin.GoPlugin{Name: "viewhtml"} | |||
p := alps.GoPlugin{Name: "viewhtml"} | |||
p.Inject("message.html", func(ctx *koushin.Context, _data koushin.RenderData) error { | |||
data := _data.(*koushinbase.MessageRenderData) | |||
p.Inject("message.html", func(ctx *alps.Context, _data alps.RenderData) error { | |||
data := _data.(*alpsbase.MessageRenderData) | |||
data.Extra["RemoteResourcesAllowed"] = ctx.QueryParam("allow-remote-resources") == "1" | |||
hasRemoteResources := false | |||
if v := ctx.Get("viewhtml.hasRemoteResources"); v != nil { | |||
@@ -32,7 +32,7 @@ func init() { | |||
return nil | |||
}) | |||
p.GET("/proxy", func(ctx *koushin.Context) error { | |||
p.GET("/proxy", func(ctx *alps.Context) error { | |||
if !proxyEnabled { | |||
return echo.NewHTTPError(http.StatusForbidden, "proxy disabled") | |||
} | |||
@@ -67,5 +67,5 @@ func init() { | |||
return ctx.Stream(http.StatusOK, mediaType, &lr) | |||
}) | |||
koushin.RegisterPluginLoader(p.Loader()) | |||
alps.RegisterPluginLoader(p.Loader()) | |||
} |
@@ -1,4 +1,4 @@ | |||
package koushinviewhtml | |||
package alpsviewhtml | |||
import ( | |||
"bytes" | |||
@@ -7,7 +7,7 @@ import ( | |||
"regexp" | |||
"strings" | |||
koushinbase "git.sr.ht/~emersion/koushin/plugins/base" | |||
alpsbase "git.sr.ht/~emersion/alps/plugins/base" | |||
"github.com/aymerick/douceur/css" | |||
cssparser "github.com/chris-ramon/douceur/parser" | |||
"github.com/microcosm-cc/bluemonday" | |||
@@ -71,7 +71,7 @@ var allowedStyles = map[string]bool{ | |||
} | |||
type sanitizer struct { | |||
msg *koushinbase.IMAPMessage | |||
msg *alpsbase.IMAPMessage | |||
allowRemoteResources bool | |||
hasRemoteResources bool | |||
} | |||
@@ -1,4 +1,4 @@ | |||
package koushinviewhtml | |||
package alpsviewhtml | |||
import ( | |||
"bytes" | |||
@@ -7,8 +7,8 @@ import ( | |||
"io/ioutil" | |||
"strings" | |||
"git.sr.ht/~emersion/koushin" | |||
koushinbase "git.sr.ht/~emersion/koushin/plugins/base" | |||
"git.sr.ht/~emersion/alps" | |||
alpsbase "git.sr.ht/~emersion/alps/plugins/base" | |||
"github.com/emersion/go-message" | |||
) | |||
@@ -24,7 +24,7 @@ var tpl = template.Must(template.New("view-html.html").Parse(tplSrc)) | |||
type viewer struct{} | |||
func (viewer) ViewMessagePart(ctx *koushin.Context, msg *koushinbase.IMAPMessage, part *message.Entity) (interface{}, error) { | |||
func (viewer) ViewMessagePart(ctx *alps.Context, msg *alpsbase.IMAPMessage, part *message.Entity) (interface{}, error) { | |||
allowRemoteResources := ctx.QueryParam("allow-remote-resources") == "1" | |||
mimeType, _, err := part.Header.ContentType() | |||
@@ -32,7 +32,7 @@ func (viewer) ViewMessagePart(ctx *koushin.Context, msg *koushinbase.IMAPMessage | |||
return nil, err | |||
} | |||
if !strings.EqualFold(mimeType, "text/html") { | |||
return nil, koushinbase.ErrViewUnsupported | |||
return nil, alpsbase.ErrViewUnsupported | |||
} | |||
body, err := ioutil.ReadAll(part.Body) | |||
@@ -61,5 +61,5 @@ func (viewer) ViewMessagePart(ctx *koushin.Context, msg *koushinbase.IMAPMessage | |||
} | |||
func init() { | |||
koushinbase.RegisterViewer(viewer{}) | |||
alpsbase.RegisterViewer(viewer{}) | |||
} |
@@ -1,10 +1,10 @@ | |||
package koushinviewtext | |||
package alpsviewtext | |||
import ( | |||
"git.sr.ht/~emersion/koushin" | |||
"git.sr.ht/~emersion/alps" | |||
) | |||
func init() { | |||
p := koushin.GoPlugin{Name: "viewtext"} | |||
koushin.RegisterPluginLoader(p.Loader()) | |||
p := alps.GoPlugin{Name: "viewtext"} | |||
alps.RegisterPluginLoader(p.Loader()) | |||
} |
@@ -1,4 +1,4 @@ | |||
package koushinviewtext | |||
package alpsviewtext | |||
import ( | |||
"bufio" | |||
@@ -7,8 +7,8 @@ import ( | |||
"net/url" | |||
"strings" | |||
"git.sr.ht/~emersion/koushin" | |||
koushinbase "git.sr.ht/~emersion/koushin/plugins/base" | |||
"git.sr.ht/~emersion/alps" | |||
alpsbase "git.sr.ht/~emersion/alps/plugins/base" | |||
"github.com/emersion/go-message" | |||
"gitlab.com/golang-commonmark/linkify" | |||
) | |||
@@ -53,13 +53,13 @@ func executeTemplate(name string, data interface{}) (template.HTML, error) { | |||
type viewer struct{} | |||
func (viewer) ViewMessagePart(ctx *koushin.Context, msg *koushinbase.IMAPMessage, part *message.Entity) (interface{}, error) { | |||
func (viewer) ViewMessagePart(ctx *alps.Context, msg *alpsbase.IMAPMessage, part *message.Entity) (interface{}, error) { | |||
mimeType, _, err := part.Header.ContentType() | |||
if err != nil { | |||
return nil, err | |||
} | |||
if !strings.EqualFold(mimeType, "text/plain") { | |||
return nil, koushinbase.ErrViewUnsupported | |||
return nil, alpsbase.ErrViewUnsupported | |||
} | |||
var tokens []interface{} | |||
@@ -114,5 +114,5 @@ func (viewer) ViewMessagePart(ctx *koushin.Context, msg *koushinbase.IMAPMessage | |||
} | |||
func init() { | |||
koushinbase.RegisterViewer(viewer{}) | |||
alpsbase.RegisterViewer(viewer{}) | |||
} |
@@ -1,4 +1,4 @@ | |||
package koushin | |||
package alps | |||
import ( | |||
"fmt" | |||
@@ -60,7 +60,7 @@ type RenderData interface { | |||
// } | |||
// | |||
// data := &MyRenderData{ | |||
// BaseRenderData: *koushin.NewBaseRenderData(ctx), | |||
// BaseRenderData: *alps.NewBaseRenderData(ctx), | |||
// // other fields... | |||
// } | |||
func NewBaseRenderData(ctx *Context) *BaseRenderData { | |||
@@ -1,4 +1,4 @@ | |||
package koushin | |||
package alps | |||
import ( | |||
"fmt" | |||
@@ -11,9 +11,9 @@ import ( | |||
"github.com/labstack/echo/v4" | |||
) | |||
const cookieName = "koushin_session" | |||
const cookieName = "alps_session" | |||
// Server holds all the koushin server state. | |||
// Server holds all the alps server state. | |||
type Server struct { | |||
e *echo.Echo | |||
Sessions *SessionManager | |||
@@ -237,7 +237,7 @@ func (s *Server) Logger() echo.Logger { | |||
// | |||
// Use a type assertion to get it from a echo.Context: | |||
// | |||
// ctx := ectx.(*koushin.Context) | |||
// ctx := ectx.(*alps.Context) | |||
type Context struct { | |||
echo.Context | |||
Server *Server | |||
@@ -1,4 +1,4 @@ | |||
package koushin | |||
package alps | |||
import ( | |||
"crypto/rand" | |||
@@ -1,4 +1,4 @@ | |||
package koushin | |||
package alps | |||
import ( | |||
"fmt" | |||
@@ -1,4 +1,4 @@ | |||
package koushin | |||
package alps | |||
import ( | |||
"encoding/json" | |||
@@ -12,7 +12,7 @@ import ( | |||
) | |||
// ErrNoStoreEntry is returned by Store.Get when the entry doesn't exist. | |||
var ErrNoStoreEntry = fmt.Errorf("koushin: no such entry in store") | |||
var ErrNoStoreEntry = fmt.Errorf("alps: no such entry in store") | |||
// Store allows storing per-user persistent data. | |||
// | |||
@@ -72,14 +72,14 @@ type imapStore struct { | |||
cache *memoryStore | |||
} | |||
var errIMAPMetadataUnsupported = fmt.Errorf("koushin: IMAP server doesn't support METADATA extension") | |||
var errIMAPMetadataUnsupported = fmt.Errorf("alps: IMAP server doesn't support METADATA extension") | |||
func newIMAPStore(session *Session) (*imapStore, error) { | |||
err := session.DoIMAP(func(c *imapclient.Client) error { | |||
mc := imapmetadata.NewClient(c) | |||
ok, err := mc.SupportMetadata() | |||
if err != nil { | |||
return fmt.Errorf("koushin: failed to check for IMAP METADATA support: %v", err) | |||
return fmt.Errorf("alps: failed to check for IMAP METADATA support: %v", err) | |||
} | |||
if !ok { | |||
return errIMAPMetadataUnsupported | |||
@@ -93,7 +93,7 @@ func newIMAPStore(session *Session) (*imapStore, error) { | |||
} | |||
func (s *imapStore) key(key string) string { | |||
return "/private/vendor/koushin/" + key | |||
return "/private/vendor/alps/" + key | |||
} | |||
func (s *imapStore) Get(key string, out interface{}) error { | |||
@@ -109,14 +109,14 @@ func (s *imapStore) Get(key string, out interface{}) error { | |||
return err | |||
}) | |||
if err != nil { | |||
return fmt.Errorf("koushin: failed to fetch IMAP store entry %q: %v", key, err) | |||
return fmt.Errorf("alps: failed to fetch IMAP store entry %q: %v", key, err) | |||
} | |||
v, ok := entries[s.key(key)] | |||
if !ok { | |||
return ErrNoStoreEntry | |||
} | |||
if err := json.Unmarshal([]byte(v), out); err != nil { | |||
return fmt.Errorf("koushin: failed to unmarshal IMAP store entry %q: %v", key, err) | |||
return fmt.Errorf("alps: failed to unmarshal IMAP store entry %q: %v", key, err) | |||
} | |||
return s.cache.Put(key, out) | |||
} | |||
@@ -124,7 +124,7 @@ func (s *imapStore) Get(key string, out interface{}) error { | |||
func (s *imapStore) Put(key string, v interface{}) error { | |||
b, err := json.Marshal(v) | |||
if err != nil { | |||
return fmt.Errorf("koushin: failed to marshal IMAP store entry %q: %v", key, err) | |||
return fmt.Errorf("alps: failed to marshal IMAP store entry %q: %v", key, err) | |||
} | |||
entries := map[string]string{ | |||
s.key(key): string(b), | |||
@@ -134,7 +134,7 @@ func (s *imapStore) Put(key string, v interface{}) error { | |||
return mc.SetMetadata("", entries) | |||
}) | |||
if err != nil { | |||
return fmt.Errorf("koushin: failed to put IMAP store entry %q: %v", key, err) | |||
return fmt.Errorf("alps: failed to put IMAP store entry %q: %v", key, err) | |||
} | |||
return s.cache.Put(key, v) | |||
@@ -1,5 +1,5 @@ | |||
{{template "head.html"}} | |||
<h1>koushin webmail</h1> | |||
<h1>alps webmail</h1> | |||
<form method="post" action="/login"> | |||
<p> | |||
@@ -4,7 +4,7 @@ | |||
<meta charset="utf-8"> | |||
<meta name="viewport" content="width=device-width, initial-scale=1"> | |||
<meta name="theme-color" content="#ffffff"> | |||
<title>koushin webmail</title> | |||
<title>alps webmail</title> | |||
<link rel="stylesheet" href="/themes/sourcehut/assets/style.css"> | |||
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QIGCC8n92KyhQAAAj1QTFRFAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////anIwUQAAAL50Uk5TAAECAwQFBgcICQoLDA4PEBESExQVFhcYGRobHB0eHyAhIyQmJygpKistLzAzNDU2Nzg5Ozw9QEFDREZHSElLTE1OT1BRVFdYWVpbXF1eX2BhZGZnaGltbnBxdHV3eHp7fn+AgYKDhIWGh4iJio2TlJucnqGio6Smp6ipqqusrbCxsrO0tre4ury9vr/Cw8TFxsfIycrMzc7P0dLT1dbY2dvf4OLj5OXm5+jq6+zt7u/w8fL09fb3+Pn6+/z9/gNzyOkAAAABYktHRL6k3IPDAAAFwUlEQVQYGe3B+VtUVQAG4G9i0TQZZyA1S0JxydzDNFTUqXBfcylzS8UE21TMyjAQUQnFEi0BHQU3cAc0UGbm+9v65Zw7y70zc++dc3qenof3xZAhQ4b8T+V/uGn/kdrm1psdHTdbm2uP7Ns434//yLD5e+u7aOH+6T0ludBszGf1L5jC87rNBdDm9XXnw0wrfG71cOhQ9N0z2vTk20Ko9t5PYToQOj4FKr17IkKHwtUToMobVQN0of/rkVDik7t06c5yZC7/V1rqajq6f9vKQFlZYNW2A0cvdNPSL35kaPEDmgxerFiSjwQFZQebQzTpKkUmsg4xUX/Nijwk4V15aoAJIhVZcM13ngnatuQhJe/WNiY464VLE28xXsMCD9LyLDzLeMEiuDLzIeM0zIRNs88xTvcMuFDSx1htC+FA6XXG6pkLx0qeM8aLHdlwJGfnP4zRNw8OzexjjOaJcGzSn4zR8z4cmfiQUZF9WXAhuyLCqO4iOOC7xahni+BSWQ+jgl7YlnWeUR3FcG1qJ6MasmDXIUZdexMZGNvGqAOwaTGjrnqREd/fNERKYUvBAxqueZEhXzsNXX7Y8SsNHW8iY+M6afgZNnxKw7NiKDCtl4YA0nrjHqXIIiixjIY7I5FOFQ37oEglDZVIo+glpeYsKJJ9mdJAIVI7QenFRChT3E+pGim9F6G0AwrtohSeglR+otSWDYVygpSqkUJRmNJCKLWEUmgCkvuOUgPU8vxO6TCSev0ZpZlQ7ANKj4YhmXWUGqBcI6UVSOY8pQVQbhGlM0hiTJhCmwfKeW5QCPlh7TNKW6DBdkobYa2eQn8eNPC9pPAbLA17QaEGWtRS6MuBlfmUVkCLtZTmwspeCoN50MIXprATVuopXIQmLRROwUoXhQpoUkXhNizkU1oCTZZRyoPZh5Tyock4SnNgtolCF3TxPKKwBmb7KTRBm0sUdsPsCIWj0OY4hR9gVkthP7SppHASZs0UtkGbLyg0wqyVwkpos4FCC8xuUghAm3IK7TDroFAGbQIUgjDroFAGbQIUgjC7SSEAbcoptMOslcIqaLOBQgvMmilsgzY7KDTCrJbCAWhTReEkzI5QOAptfqTwPcz2UbgAbS5R2AWzjRS6oYvnMYXVMJtPqQCavEVpNsz8lMqgyXJKo2DhPoWD0OQwhU5YOU2hGZpcoVADK3sohLzQwh+h8CWslFBaCS3WUZoDK7nPKZyCFnUUerNh6TSFAS808L+iUANrmylthQafU1oPawVhCm0eKOcJUhgcjSTOUVoI5RZTqkMyqymdhXJNlMqRzPAnlGZDsRJKD3OR1LeUzkEtTxOlSiRXGKL0EZRaSmlwPFI4Tul6DhTKvUXpGFKZEqa0EwrtoRQqRkrVlP6ZBGUmD1A6htQm9FP6IxuK5Fyl1P8O0viahgoo8g0NB5HOyDuUImVQ4mMabo9AWgEaeqZCgel9NCyFDb/Q0DkWGXv7Lg0nYIe/i4Y2HzKUf4OGe6NhS2mEhr98yEh+Kw3hBbCpglHt45CB8TcY9RXsyjrLqM5pcG36XUadfg22eYOM6l0Glz7uY9T1UXCg6AFjHMqGCznfMMb9Qjgyo4cxLhfDsclXGePpdDg0r48x+nflwJHcPQOM0TsLjs3rYazgEtjnWXqLsZ7Oggszuhnn9w9gU0kT49yfDleKgozXuMiDtDyLmxjveiFc8jYwwY3tPqTk/zzIBPWj4FpWRYQJXtau9SEJ/7q6V0wQ/uo1ZKK0iybhlqplYxHP89byw1ciNLm3ABny/0xLjy4dr/xiQ3kgUL5hR9WPlx7T0onRyFzgDl26vRRKjKwcoAv9B0dAlcLqMB0KH3sHKk2pDtGBwWPFUG3C4Ue06WHleOgwbMWZENMarCvPhTb+jb/1MYXemvWjoVnO3J2nbtNCZ82Xc7LxH8mbs2b3DycbW9qDwfaWxpPf71o9exSGDBky5P/pX9F6dsCMuJp+AAAAAElFTkSuQmCC" /> | |||
</head> | |||
@@ -1,7 +1,7 @@ | |||
<nav class="container-fluid navbar navbar-light navbar-expand-sm"> | |||
<!-- TODO: show active plugin name --> | |||
<a class="navbar-brand" href="/"> | |||
koushin | |||
alps | |||
<span class="text-danger">mail</span> | |||
</a> | |||
{{if .LoggedIn}} | |||