Rename project to alps

This commit is contained in:
Simon Ser 2020-05-13 14:07:44 +02:00
parent 4cf5ad68af
commit b891a95fcf
No known key found for this signature in database
GPG Key ID: 0FDE7BE0E88F5E48
52 changed files with 218 additions and 218 deletions

View File

@ -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. 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]): 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: 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 Add `-theme sourcehut` to use the SourceHut theme. See `docs/cli.md` for more
information. information.
When developing themes and plugins, the script `contrib/hotreload.sh` can be 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 ## Contributing
@ -29,6 +29,6 @@ Send patches on the [mailing list], report bugs on the [issue tracker].
MIT MIT
[RFC 6186]: https://tools.ietf.org/html/rfc6186 [RFC 6186]: https://tools.ietf.org/html/rfc6186
[Go plugin helpers]: https://godoc.org/git.sr.ht/~emersion/koushin#GoPlugin [Go plugin helpers]: https://godoc.org/git.sr.ht/~emersion/alps#GoPlugin
[mailing list]: https://lists.sr.ht/~sircmpwn/koushin [mailing list]: https://lists.sr.ht/~sircmpwn/alps
[issue tracker]: https://todo.sr.ht/~sircmpwn/koushin [issue tracker]: https://todo.sr.ht/~sircmpwn/alps

View File

@ -7,28 +7,28 @@ import (
"os/signal" "os/signal"
"syscall" "syscall"
"git.sr.ht/~emersion/koushin" "git.sr.ht/~emersion/alps"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware" "github.com/labstack/echo/v4/middleware"
"github.com/labstack/gommon/log" "github.com/labstack/gommon/log"
_ "git.sr.ht/~emersion/koushin/plugins/base" _ "git.sr.ht/~emersion/alps/plugins/base"
_ "git.sr.ht/~emersion/koushin/plugins/caldav" _ "git.sr.ht/~emersion/alps/plugins/caldav"
_ "git.sr.ht/~emersion/koushin/plugins/carddav" _ "git.sr.ht/~emersion/alps/plugins/carddav"
_ "git.sr.ht/~emersion/koushin/plugins/lua" _ "git.sr.ht/~emersion/alps/plugins/lua"
_ "git.sr.ht/~emersion/koushin/plugins/viewhtml" _ "git.sr.ht/~emersion/alps/plugins/viewhtml"
_ "git.sr.ht/~emersion/koushin/plugins/viewtext" _ "git.sr.ht/~emersion/alps/plugins/viewtext"
) )
func main() { func main() {
var options koushin.Options var options alps.Options
var addr string var addr string
flag.StringVar(&options.Theme, "theme", "", "default theme") flag.StringVar(&options.Theme, "theme", "", "default theme")
flag.StringVar(&addr, "addr", ":1323", "listening address") flag.StringVar(&addr, "addr", ":1323", "listening address")
flag.BoolVar(&options.Debug, "debug", false, "enable debug logs") flag.BoolVar(&options.Debug, "debug", false, "enable debug logs")
flag.Usage = func() { 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() flag.PrintDefaults()
} }
@ -45,7 +45,7 @@ func main() {
if l, ok := e.Logger.(*log.Logger); ok { if l, ok := e.Logger.(*log.Logger); ok {
l.SetHeader("${time_rfc3339} ${level}") l.SetHeader("${time_rfc3339} ${level}")
} }
s, err := koushin.New(e, &options) s, err := alps.New(e, &options)
if err != nil { if err != nil {
e.Logger.Fatal(err) e.Logger.Fatal(err)
} }

View File

@ -1,6 +1,6 @@
#!/bin/sh #!/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 events=modify,create,delete,move
targets="themes/ plugins/" targets="themes/ plugins/"
@ -8,6 +8,6 @@ targets="themes/ plugins/"
inotifywait -e "$events" -m -r $targets | while read line; do inotifywait -e "$events" -m -r $targets | while read line; do
jobs >/dev/null # Reap status of any terminated job jobs >/dev/null # Reap status of any terminated job
if [ -z "$(jobs)" ]; then if [ -z "$(jobs)" ]; then
(sleep 0.5 && pkill -USR1 koushin) & (sleep 0.5 && pkill -USR1 alps) &
fi fi
done done

View File

@ -1,4 +1,4 @@
package koushin package alps
import ( import (
"fmt" "fmt"

View File

@ -1,22 +1,22 @@
# SYNOPSIS # SYNOPSIS
koushin [options...] <upstream servers...> alps [options...] <upstream servers...>
# DESCRIPTION # 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. SMTP and other upstream servers.
At least one upstream IMAP server needs to be specified. The easiest way to do At least one upstream IMAP server needs to be specified. The easiest way to do
so is to just specify a domain name: 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]). This assumes SRV DNS records are properly set up (see [RFC 6186]).
Alternatively, one or more upstream server URLs can be specified: 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: The following URL schemes are supported:

View File

@ -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 package exampleplugin
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"git.sr.ht/~emersion/koushin" "git.sr.ht/~emersion/alps"
koushinbase "git.sr.ht/~emersion/koushin/plugins/base" alpsbase "git.sr.ht/~emersion/alps/plugins/base"
) )
func init() { func init() {
p := koushin.GoPlugin{Name: "example"} p := alps.GoPlugin{Name: "example"}
// Setup a function called when the mailbox view is rendered // Setup a function called when the mailbox view is rendered
p.Inject("mailbox.html", func(ctx *koushin.Context, kdata koushin.RenderData) error { p.Inject("mailbox.html", func(ctx *alps.Context, kdata alps.RenderData) error {
data := kdata.(*koushinbase.MailboxRenderData) data := kdata.(*alpsbase.MailboxRenderData)
fmt.Println("The mailbox view for " + data.Mailbox.Name + " is being rendered") fmt.Println("The mailbox view for " + data.Mailbox.Name + " is being rendered")
// Set extra data that can be accessed from the mailbox.html template // Set extra data that can be accessed from the mailbox.html template
data.Extra["Example"] = "Hi from Go" data.Extra["Example"] = "Hi from Go"
@ -24,7 +24,7 @@ func init() {
}) })
// Wire up a new route // 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.") return ctx.String(http.StatusOK, "This is an example page.")
}) })
@ -35,5 +35,5 @@ func init() {
}, },
}) })
koushin.RegisterPluginLoader(p.Loader()) alps.RegisterPluginLoader(p.Loader())
} }

View File

@ -2,18 +2,18 @@
print("Hi, this is an example Lua plugin") print("Hi, this is an example Lua plugin")
-- Setup a function called when the mailbox view is rendered -- 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") print("The mailbox view for " .. data.Mailbox.Name .. " is being rendered")
-- Set extra data that can be accessed from the mailbox.html template -- Set extra data that can be accessed from the mailbox.html template
data.Extra.Example = "Hi from Lua" data.Extra.Example = "Hi from Lua"
end) end)
-- Wire up a new route -- 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.") ctx:String(200, "This is an example page.")
end) end)
-- Set a filter function that can be used from templates -- 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 return a .. " and " .. b
end) end)

View File

@ -1,21 +1,21 @@
# Running koushin with a Google account # Running alps with a Google account
## Create an application password ## 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. 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/ \ carddavs://www.googleapis.com/carddav/v1/principals/YOUREMAIL/ \
caldavs://www.google.com/calendar/dav caldavs://www.google.com/calendar/dav
Replace `YOUREMAIL` with your Google account's e-mail address. 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. password.
[app passwords]: https://security.google.com/settings/security/apppasswords [app passwords]: https://security.google.com/settings/security/apppasswords

View File

@ -17,7 +17,7 @@ Assets in `plugins/<name>/public/assets/*` are served by the HTTP server at
## Go plugins ## Go plugins
They can use the [Go plugin helpers] and need to be included at compile-time in 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 ## Lua plugins
@ -25,8 +25,8 @@ The entry point is at `plugins/<name>/main.lua`.
API: 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) `f` with the template data (the special name `*` matches all templates)
* `koushin.set_filter(name, f)`: set a template function * `alps.set_filter(name, f)`: set a template function
* `koushin.set_route(method, path, f)`: register a new HTTP route, `f` will be * `alps.set_route(method, path, f)`: register a new HTTP route, `f` will be
called with the HTTP context called with the HTTP context

2
go.mod
View File

@ -1,4 +1,4 @@
module git.sr.ht/~emersion/koushin module git.sr.ht/~emersion/alps
go 1.13 go 1.13

View File

@ -1,4 +1,4 @@
package koushin package alps
import ( import (
"fmt" "fmt"

View File

@ -1,4 +1,4 @@
package koushin package alps
import ( import (
"html/template" "html/template"
@ -9,7 +9,7 @@ import (
// PluginDir is the path to the plugins directory. // PluginDir is the path to the plugins directory.
const PluginDir = "plugins" const PluginDir = "plugins"
// Plugin extends koushin with additional functionality. // Plugin extends alps with additional functionality.
type Plugin interface { type Plugin interface {
// Name should return the plugin name. // Name should return the plugin name.
Name() string Name() string

View File

@ -1,4 +1,4 @@
package koushin package alps
import ( import (
"html/template" "html/template"
@ -71,7 +71,7 @@ type goPluginRoute struct {
// //
// p := GoPlugin{Name: "my-plugin"} // p := GoPlugin{Name: "my-plugin"}
// // Define routes, template functions, etc // // Define routes, template functions, etc
// koushin.RegisterPluginLoader(p.Loader()) // alps.RegisterPluginLoader(p.Loader())
type GoPlugin struct { type GoPlugin struct {
Name string Name string

View File

@ -1,4 +1,4 @@
package koushinbase package alpsbase
import ( import (
"bufio" "bufio"

View File

@ -1,14 +1,14 @@
package koushinbase package alpsbase
import ( import (
"git.sr.ht/~emersion/koushin" "git.sr.ht/~emersion/alps"
) )
func init() { func init() {
p := koushin.GoPlugin{Name: "base"} p := alps.GoPlugin{Name: "base"}
p.TemplateFuncs(templateFuncs) p.TemplateFuncs(templateFuncs)
registerRoutes(&p) registerRoutes(&p)
koushin.RegisterPluginLoader(p.Loader()) alps.RegisterPluginLoader(p.Loader())
} }

View File

@ -1,6 +1,6 @@
{{template "head.html"}} {{template "head.html"}}
<h1>koushin</h1> <h1>alps</h1>
<p> <p>
<a href="/mailbox/INBOX">Back</a> <a href="/mailbox/INBOX">Back</a>

View File

@ -2,6 +2,6 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>koushin</title> <title>alps</title>
</head> </head>
<body> <body>

View File

@ -1,6 +1,6 @@
{{template "head.html"}} {{template "head.html"}}
<h1>koushin</h1> <h1>alps</h1>
<form method="post" action=""> <form method="post" action="">
<label for="username">Username:</label> <label for="username">Username:</label>

View File

@ -1,6 +1,6 @@
{{template "head.html"}} {{template "head.html"}}
<h1>koushin</h1> <h1>alps</h1>
<p> <p>
<a href="/logout">Logout</a> <a href="/logout">Logout</a>

View File

@ -1,6 +1,6 @@
{{template "head.html"}} {{template "head.html"}}
<h1>koushin</h1> <h1>alps</h1>
<p> <p>
<a href="/mailbox/{{.Mailbox.Name | pathescape}}?page={{.MailboxPage}}"> <a href="/mailbox/{{.Mailbox.Name | pathescape}}?page={{.MailboxPage}}">

View File

@ -1,6 +1,6 @@
{{template "head.html"}} {{template "head.html"}}
<h1>koushin</h1> <h1>alps</h1>
<p> <p>
<a href="/mailbox/INBOX">Back</a> <a href="/mailbox/INBOX">Back</a>

View File

@ -1,4 +1,4 @@
package koushinbase package alpsbase
import ( import (
"bytes" "bytes"
@ -11,7 +11,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"git.sr.ht/~emersion/koushin" "git.sr.ht/~emersion/alps"
"github.com/emersion/go-imap" "github.com/emersion/go-imap"
imapmove "github.com/emersion/go-imap-move" imapmove "github.com/emersion/go-imap-move"
imapclient "github.com/emersion/go-imap/client" imapclient "github.com/emersion/go-imap/client"
@ -21,18 +21,18 @@ import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
func registerRoutes(p *koushin.GoPlugin) { func registerRoutes(p *alps.GoPlugin) {
p.GET("/", func(ctx *koushin.Context) error { p.GET("/", func(ctx *alps.Context) error {
return ctx.Redirect(http.StatusFound, "/mailbox/INBOX") return ctx.Redirect(http.StatusFound, "/mailbox/INBOX")
}) })
p.GET("/mailbox/:mbox", handleGetMailbox) p.GET("/mailbox/:mbox", handleGetMailbox)
p.POST("/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) 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) return handleGetPart(ctx, true)
}) })
@ -64,7 +64,7 @@ func registerRoutes(p *koushin.GoPlugin) {
} }
type MailboxRenderData struct { type MailboxRenderData struct {
koushin.BaseRenderData alps.BaseRenderData
Mailbox *MailboxStatus Mailbox *MailboxStatus
Mailboxes []MailboxInfo Mailboxes []MailboxInfo
Messages []IMAPMessage Messages []IMAPMessage
@ -72,7 +72,7 @@ type MailboxRenderData struct {
Query string Query string
} }
func handleGetMailbox(ctx *koushin.Context) error { func handleGetMailbox(ctx *alps.Context) error {
mboxName, err := url.PathUnescape(ctx.Param("mbox")) mboxName, err := url.PathUnescape(ctx.Param("mbox"))
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err) return echo.NewHTTPError(http.StatusBadRequest, err)
@ -136,7 +136,7 @@ func handleGetMailbox(ctx *koushin.Context) error {
} }
return ctx.Render(http.StatusOK, "mailbox.html", &MailboxRenderData{ return ctx.Render(http.StatusOK, "mailbox.html", &MailboxRenderData{
BaseRenderData: *koushin.NewBaseRenderData(ctx), BaseRenderData: *alps.NewBaseRenderData(ctx),
Mailbox: mbox, Mailbox: mbox,
Mailboxes: mailboxes, Mailboxes: mailboxes,
Messages: msgs, 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") username := ctx.FormValue("username")
password := ctx.FormValue("password") password := ctx.FormValue("password")
if username != "" && password != "" { if username != "" && password != "" {
s, err := ctx.Server.Sessions.Put(username, password) s, err := ctx.Server.Sessions.Put(username, password)
if err != nil { if err != nil {
if _, ok := err.(koushin.AuthError); ok { if _, ok := err.(alps.AuthError); ok {
return ctx.Render(http.StatusOK, "login.html", nil) return ctx.Render(http.StatusOK, "login.html", nil)
} }
return fmt.Errorf("failed to put connection in pool: %v", err) 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.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.Session.Close()
ctx.SetSession(nil) ctx.SetSession(nil)
return ctx.Redirect(http.StatusFound, "/login") return ctx.Redirect(http.StatusFound, "/login")
} }
type MessageRenderData struct { type MessageRenderData struct {
koushin.BaseRenderData alps.BaseRenderData
Mailboxes []MailboxInfo Mailboxes []MailboxInfo
Mailbox *MailboxStatus Mailbox *MailboxStatus
Message *IMAPMessage Message *IMAPMessage
@ -185,7 +185,7 @@ type MessageRenderData struct {
Flags map[string]bool 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")) mboxName, uid, err := parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid"))
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err) 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{ return ctx.Render(http.StatusOK, "message.html", &MessageRenderData{
BaseRenderData: *koushin.NewBaseRenderData(ctx), BaseRenderData: *alps.NewBaseRenderData(ctx),
Mailboxes: mailboxes, Mailboxes: mailboxes,
Mailbox: mbox, Mailbox: mbox,
Message: msg, Message: msg,
@ -283,7 +283,7 @@ func handleGetPart(ctx *koushin.Context, raw bool) error {
} }
type ComposeRenderData struct { type ComposeRenderData struct {
koushin.BaseRenderData alps.BaseRenderData
Message *OutgoingMessage Message *OutgoingMessage
} }
@ -300,12 +300,12 @@ type composeOptions struct {
// Send message, append it to the Sent mailbox, mark the original message as // Send message, append it to the Sent mailbox, mark the original message as
// answered // 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 { err := ctx.Session.DoSMTP(func(c *smtp.Client) error {
return sendMessage(c, msg) return sendMessage(c, msg)
}) })
if err != nil { if err != nil {
if _, ok := err.(koushin.AuthError); ok { if _, ok := err.(alps.AuthError); ok {
return echo.NewHTTPError(http.StatusForbidden, err) return echo.NewHTTPError(http.StatusForbidden, err)
} }
return fmt.Errorf("failed to send message: %v", 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") 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(), '@') { if msg.From == "" && strings.ContainsRune(ctx.Session.Username(), '@') {
msg.From = 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{ return ctx.Render(http.StatusOK, "compose.html", &ComposeRenderData{
BaseRenderData: *koushin.NewBaseRenderData(ctx), BaseRenderData: *alps.NewBaseRenderData(ctx),
Message: msg, Message: msg,
}) })
} }
func handleComposeNew(ctx *koushin.Context) error { func handleComposeNew(ctx *alps.Context) error {
// These are common mailto URL query parameters // These are common mailto URL query parameters
// TODO: cc, bcc // TODO: cc, bcc
return handleCompose(ctx, &OutgoingMessage{ return handleCompose(ctx, &OutgoingMessage{
@ -462,7 +462,7 @@ func unwrapIMAPAddressList(addrs []*imap.Address) []string {
return l return l
} }
func handleReply(ctx *koushin.Context) error { func handleReply(ctx *alps.Context) error {
var inReplyToPath messagePath var inReplyToPath messagePath
var err error var err error
inReplyToPath.Mailbox, inReplyToPath.Uid, err = parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid")) 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}) return handleCompose(ctx, &msg, &composeOptions{InReplyTo: &inReplyToPath})
} }
func handleForward(ctx *koushin.Context) error { func handleForward(ctx *alps.Context) error {
var sourcePath messagePath var sourcePath messagePath
var err error var err error
sourcePath.Mailbox, sourcePath.Uid, err = parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid")) 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}) return handleCompose(ctx, &msg, &composeOptions{Forward: &sourcePath})
} }
func handleEdit(ctx *koushin.Context) error { func handleEdit(ctx *alps.Context) error {
var sourcePath messagePath var sourcePath messagePath
var err error var err error
sourcePath.Mailbox, sourcePath.Uid, err = parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid")) 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}) 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 != "" { if v := ctx.FormValue(k); v != "" {
return v return v
} }
return ctx.QueryParam(k) return ctx.QueryParam(k)
} }
func handleMove(ctx *koushin.Context) error { func handleMove(ctx *alps.Context) error {
mboxName, err := url.PathUnescape(ctx.Param("mbox")) mboxName, err := url.PathUnescape(ctx.Param("mbox"))
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err) 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))) 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")) mboxName, err := url.PathUnescape(ctx.Param("mbox"))
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err) 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))) 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")) mboxName, err := url.PathUnescape(ctx.Param("mbox"))
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err) return echo.NewHTTPError(http.StatusBadRequest, err)
@ -840,11 +840,11 @@ type Settings struct {
MessagesPerPage int MessagesPerPage int
} }
func loadSettings(s koushin.Store) (*Settings, error) { func loadSettings(s alps.Store) (*Settings, error) {
settings := &Settings{ settings := &Settings{
MessagesPerPage: 50, 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 return nil, err
} }
if err := settings.check(); err != nil { if err := settings.check(); err != nil {
@ -861,11 +861,11 @@ func (s *Settings) check() error {
} }
type SettingsRenderData struct { type SettingsRenderData struct {
koushin.BaseRenderData alps.BaseRenderData
Settings *Settings Settings *Settings
} }
func handleSettings(ctx *koushin.Context) error { func handleSettings(ctx *alps.Context) error {
settings, err := loadSettings(ctx.Session.Store()) settings, err := loadSettings(ctx.Session.Store())
if err != nil { if err != nil {
return fmt.Errorf("failed to load settings: %v", err) 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{ return ctx.Render(http.StatusOK, "settings.html", &SettingsRenderData{
BaseRenderData: *koushin.NewBaseRenderData(ctx), BaseRenderData: *alps.NewBaseRenderData(ctx),
Settings: settings, Settings: settings,
}) })
} }

View File

@ -1,4 +1,4 @@
package koushinbase package alpsbase
import ( import (
"bufio" "bufio"

View File

@ -1,4 +1,4 @@
package koushinbase package alpsbase
import ( import (
"fmt" "fmt"

View File

@ -1,4 +1,4 @@
package koushinbase package alpsbase
import ( import (
"html/template" "html/template"

View File

@ -1,9 +1,9 @@
package koushinbase package alpsbase
import ( import (
"fmt" "fmt"
"git.sr.ht/~emersion/koushin" "git.sr.ht/~emersion/alps"
"github.com/emersion/go-message" "github.com/emersion/go-message"
) )
@ -16,7 +16,7 @@ type Viewer interface {
// ViewMessagePart renders a message part. The returned value is displayed // ViewMessagePart renders a message part. The returned value is displayed
// in a template. ErrViewUnsupported is returned if the message part isn't // in a template. ErrViewUnsupported is returned if the message part isn't
// supported. // supported.
ViewMessagePart(*koushin.Context, *IMAPMessage, *message.Entity) (interface{}, error) ViewMessagePart(*alps.Context, *IMAPMessage, *message.Entity) (interface{}, error)
} }
var viewers []Viewer var viewers []Viewer
@ -26,7 +26,7 @@ func RegisterViewer(viewer Viewer) {
viewers = append(viewers, 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 { for _, viewer := range viewers {
v, err := viewer.ViewMessagePart(ctx, msg, part) v, err := viewer.ViewMessagePart(ctx, msg, part)
if err == ErrViewUnsupported { if err == ErrViewUnsupported {

View File

@ -1,11 +1,11 @@
package koushincaldav package alpscaldav
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"git.sr.ht/~emersion/koushin" "git.sr.ht/~emersion/alps"
"github.com/emersion/go-webdav/caldav" "github.com/emersion/go-webdav/caldav"
) )
@ -13,7 +13,7 @@ var errNoCalendar = fmt.Errorf("caldav: no calendar found")
type authRoundTripper struct { type authRoundTripper struct {
upstream http.RoundTripper upstream http.RoundTripper
session *koushin.Session session *alps.Session
} }
func (rt *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 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) 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{ rt := authRoundTripper{
upstream: http.DefaultTransport, upstream: http.DefaultTransport,
session: session, session: session,
@ -34,7 +34,7 @@ func newClient(u *url.URL, session *koushin.Session) (*caldav.Client, error) {
return c, nil 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) c, err := newClient(u, session)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err

View File

@ -1,11 +1,11 @@
package koushincaldav package alpscaldav
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"git.sr.ht/~emersion/koushin" "git.sr.ht/~emersion/alps"
) )
func sanityCheckURL(u *url.URL) error { func sanityCheckURL(u *url.URL) error {
@ -27,9 +27,9 @@ func sanityCheckURL(u *url.URL) error {
return nil 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") 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 return nil, nil
} else if err != nil { } else if err != nil {
return nil, fmt.Errorf("caldav: failed to parse upstream caldav server: %v", err) 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) srv.Logger().Printf("Configured upstream CalDAV server: %v", u)
p := koushin.GoPlugin{Name: "caldav"} p := alps.GoPlugin{Name: "caldav"}
registerRoutes(&p, u) registerRoutes(&p, u)
@ -61,7 +61,7 @@ func newPlugin(srv *koushin.Server) (koushin.Plugin, error) {
} }
func init() { func init() {
koushin.RegisterPluginLoader(func(s *koushin.Server) ([]koushin.Plugin, error) { alps.RegisterPluginLoader(func(s *alps.Server) ([]alps.Plugin, error) {
p, err := newPlugin(s) p, err := newPlugin(s)
if err != nil { if err != nil {
return nil, err return nil, err
@ -69,6 +69,6 @@ func init() {
if p == nil { if p == nil {
return nil, nil return nil, nil
} }
return []koushin.Plugin{p}, err return []alps.Plugin{p}, err
}) })
} }

View File

@ -1,6 +1,6 @@
{{template "head.html"}} {{template "head.html"}}
<h1>koushin</h1> <h1>alps</h1>
<p> <p>
<a href="/">Back</a> <a href="/">Back</a>

View File

@ -1,6 +1,6 @@
{{template "head.html"}} {{template "head.html"}}
<h1>koushin</h1> <h1>alps</h1>
<p> <p>
<a href="/calendar">Back</a> <a href="/calendar">Back</a>

View File

@ -1,4 +1,4 @@
package koushincaldav package alpscaldav
import ( import (
"fmt" "fmt"
@ -6,12 +6,12 @@ import (
"net/url" "net/url"
"time" "time"
"git.sr.ht/~emersion/koushin" "git.sr.ht/~emersion/alps"
"github.com/emersion/go-webdav/caldav" "github.com/emersion/go-webdav/caldav"
) )
type CalendarRenderData struct { type CalendarRenderData struct {
koushin.BaseRenderData alps.BaseRenderData
Time time.Time Time time.Time
Calendar *caldav.Calendar Calendar *caldav.Calendar
Events []caldav.CalendarObject Events []caldav.CalendarObject
@ -19,15 +19,15 @@ type CalendarRenderData struct {
} }
type EventRenderData struct { type EventRenderData struct {
koushin.BaseRenderData alps.BaseRenderData
Calendar *caldav.Calendar Calendar *caldav.Calendar
Event *caldav.CalendarObject Event *caldav.CalendarObject
} }
var monthPageLayout = "2006-01" var monthPageLayout = "2006-01"
func registerRoutes(p *koushin.GoPlugin, u *url.URL) { func registerRoutes(p *alps.GoPlugin, u *url.URL) {
p.GET("/calendar", func(ctx *koushin.Context) error { p.GET("/calendar", func(ctx *alps.Context) error {
var start time.Time var start time.Time
if s := ctx.QueryParam("month"); s != "" { if s := ctx.QueryParam("month"); s != "" {
var err error var err error
@ -77,7 +77,7 @@ func registerRoutes(p *koushin.GoPlugin, u *url.URL) {
} }
return ctx.Render(http.StatusOK, "calendar.html", &CalendarRenderData{ return ctx.Render(http.StatusOK, "calendar.html", &CalendarRenderData{
BaseRenderData: *koushin.NewBaseRenderData(ctx), BaseRenderData: *alps.NewBaseRenderData(ctx),
Time: start, Time: start,
Calendar: calendar, Calendar: calendar,
Events: events, 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") uid := ctx.Param("uid")
c, calendar, err := getCalendar(u, ctx.Session) c, calendar, err := getCalendar(u, ctx.Session)
@ -131,7 +131,7 @@ func registerRoutes(p *koushin.GoPlugin, u *url.URL) {
event := &events[0] event := &events[0]
return ctx.Render(http.StatusOK, "event.html", &EventRenderData{ return ctx.Render(http.StatusOK, "event.html", &EventRenderData{
BaseRenderData: *koushin.NewBaseRenderData(ctx), BaseRenderData: *alps.NewBaseRenderData(ctx),
Calendar: calendar, Calendar: calendar,
Event: event, Event: event,
}) })

View File

@ -1,11 +1,11 @@
package koushincarddav package alpscarddav
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"git.sr.ht/~emersion/koushin" "git.sr.ht/~emersion/alps"
"github.com/emersion/go-webdav/carddav" "github.com/emersion/go-webdav/carddav"
) )
@ -13,7 +13,7 @@ var errNoAddressBook = fmt.Errorf("carddav: no address book found")
type authRoundTripper struct { type authRoundTripper struct {
upstream http.RoundTripper upstream http.RoundTripper
session *koushin.Session session *alps.Session
} }
func (rt *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 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) 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{ rt := authRoundTripper{
upstream: http.DefaultTransport, upstream: http.DefaultTransport,
session: session, session: session,

View File

@ -1,4 +1,4 @@
package koushincarddav package alpscarddav
import ( import (
"fmt" "fmt"
@ -6,8 +6,8 @@ import (
"net/url" "net/url"
"strings" "strings"
"git.sr.ht/~emersion/koushin" "git.sr.ht/~emersion/alps"
koushinbase "git.sr.ht/~emersion/koushin/plugins/base" alpsbase "git.sr.ht/~emersion/alps/plugins/base"
"github.com/emersion/go-vcard" "github.com/emersion/go-vcard"
"github.com/emersion/go-webdav/carddav" "github.com/emersion/go-webdav/carddav"
) )
@ -32,16 +32,16 @@ func sanityCheckURL(u *url.URL) error {
} }
type plugin struct { type plugin struct {
koushin.GoPlugin alps.GoPlugin
url *url.URL url *url.URL
homeSetCache map[string]string 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) 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) c, err := newClient(p.url, session)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to create CardDAV client: %v", err) 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 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") 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 return nil, nil
} else if err != nil { } else if err != nil {
return nil, fmt.Errorf("carddav: failed to parse upstream CardDAV server: %v", err) 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) srv.Logger().Printf("Configured upstream CardDAV server: %v", u)
p := &plugin{ p := &plugin{
GoPlugin: koushin.GoPlugin{Name: "carddav"}, GoPlugin: alps.GoPlugin{Name: "carddav"},
url: u, url: u,
homeSetCache: make(map[string]string), 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 { p.Inject("compose.html", func(ctx *alps.Context, _data alps.RenderData) error {
data := _data.(*koushinbase.ComposeRenderData) data := _data.(*alpsbase.ComposeRenderData)
c, addressBook, err := p.clientWithAddressBook(ctx.Session) c, addressBook, err := p.clientWithAddressBook(ctx.Session)
if err == errNoAddressBook { if err == errNoAddressBook {
@ -156,7 +156,7 @@ func newPlugin(srv *koushin.Server) (koushin.Plugin, error) {
} }
func init() { func init() {
koushin.RegisterPluginLoader(func(s *koushin.Server) ([]koushin.Plugin, error) { alps.RegisterPluginLoader(func(s *alps.Server) ([]alps.Plugin, error) {
p, err := newPlugin(s) p, err := newPlugin(s)
if err != nil { if err != nil {
return nil, err return nil, err
@ -164,6 +164,6 @@ func init() {
if p == nil { if p == nil {
return nil, nil return nil, nil
} }
return []koushin.Plugin{p}, err return []alps.Plugin{p}, err
}) })
} }

View File

@ -1,6 +1,6 @@
{{template "head.html"}} {{template "head.html"}}
<h1>koushin</h1> <h1>alps</h1>
<p> <p>
<a href="/">Back</a> · <a href="/contacts/create">Create new contact</a> <a href="/">Back</a> · <a href="/contacts/create">Create new contact</a>

View File

@ -1,6 +1,6 @@
{{template "head.html"}} {{template "head.html"}}
<h1>koushin</h1> <h1>alps</h1>
<p> <p>
<a href="/contacts">Back</a> <a href="/contacts">Back</a>

View File

@ -1,6 +1,6 @@
{{template "head.html"}} {{template "head.html"}}
<h1>koushin</h1> <h1>alps</h1>
<p> <p>
<a href="/contacts">Back</a> <a href="/contacts">Back</a>

View File

@ -1,4 +1,4 @@
package koushincarddav package alpscarddav
import ( import (
"fmt" "fmt"
@ -7,7 +7,7 @@ import (
"path" "path"
"strings" "strings"
"git.sr.ht/~emersion/koushin" "git.sr.ht/~emersion/alps"
"github.com/emersion/go-vcard" "github.com/emersion/go-vcard"
"github.com/emersion/go-webdav/carddav" "github.com/emersion/go-webdav/carddav"
"github.com/google/uuid" "github.com/google/uuid"
@ -15,19 +15,19 @@ import (
) )
type AddressBookRenderData struct { type AddressBookRenderData struct {
koushin.BaseRenderData alps.BaseRenderData
AddressBook *carddav.AddressBook AddressBook *carddav.AddressBook
AddressObjects []AddressObject AddressObjects []AddressObject
Query string Query string
} }
type AddressObjectRenderData struct { type AddressObjectRenderData struct {
koushin.BaseRenderData alps.BaseRenderData
AddressObject AddressObject AddressObject AddressObject
} }
type UpdateAddressObjectRenderData struct { type UpdateAddressObjectRenderData struct {
koushin.BaseRenderData alps.BaseRenderData
AddressObject *carddav.AddressObject // nil if creating a new contact AddressObject *carddav.AddressObject // nil if creating a new contact
Card vcard.Card Card vcard.Card
} }
@ -42,7 +42,7 @@ func parseObjectPath(s string) (string, error) {
} }
func registerRoutes(p *plugin) { func registerRoutes(p *plugin) {
p.GET("/contacts", func(ctx *koushin.Context) error { p.GET("/contacts", func(ctx *alps.Context) error {
queryText := ctx.QueryParam("query") queryText := ctx.QueryParam("query")
c, addressBook, err := p.clientWithAddressBook(ctx.Session) c, addressBook, err := p.clientWithAddressBook(ctx.Session)
@ -82,14 +82,14 @@ func registerRoutes(p *plugin) {
} }
return ctx.Render(http.StatusOK, "address-book.html", &AddressBookRenderData{ return ctx.Render(http.StatusOK, "address-book.html", &AddressBookRenderData{
BaseRenderData: *koushin.NewBaseRenderData(ctx), BaseRenderData: *alps.NewBaseRenderData(ctx),
AddressBook: addressBook, AddressBook: addressBook,
AddressObjects: newAddressObjectList(aos), AddressObjects: newAddressObjectList(aos),
Query: queryText, 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")) path, err := parseObjectPath(ctx.Param("path"))
if err != nil { if err != nil {
return err return err
@ -119,12 +119,12 @@ func registerRoutes(p *plugin) {
ao := &aos[0] ao := &aos[0]
return ctx.Render(http.StatusOK, "address-object.html", &AddressObjectRenderData{ return ctx.Render(http.StatusOK, "address-object.html", &AddressObjectRenderData{
BaseRenderData: *koushin.NewBaseRenderData(ctx), BaseRenderData: *alps.NewBaseRenderData(ctx),
AddressObject: AddressObject{ao}, AddressObject: AddressObject{ao},
}) })
}) })
updateContact := func(ctx *koushin.Context) error { updateContact := func(ctx *alps.Context) error {
addressObjectPath, err := parseObjectPath(ctx.Param("path")) addressObjectPath, err := parseObjectPath(ctx.Param("path"))
if err != nil { if err != nil {
return err return err
@ -200,7 +200,7 @@ func registerRoutes(p *plugin) {
} }
return ctx.Render(http.StatusOK, "update-address-object.html", &UpdateAddressObjectRenderData{ return ctx.Render(http.StatusOK, "update-address-object.html", &UpdateAddressObjectRenderData{
BaseRenderData: *koushin.NewBaseRenderData(ctx), BaseRenderData: *alps.NewBaseRenderData(ctx),
AddressObject: ao, AddressObject: ao,
Card: card, Card: card,
}) })

View File

@ -1,11 +1,11 @@
package koushinlua package alpslua
import ( import (
"fmt" "fmt"
"html/template" "html/template"
"path/filepath" "path/filepath"
"git.sr.ht/~emersion/koushin" "git.sr.ht/~emersion/alps"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/yuin/gopher-lua" "github.com/yuin/gopher-lua"
"layeh.com/gopher-luar" "layeh.com/gopher-luar"
@ -69,7 +69,7 @@ func (p *luaPlugin) setRoute(l *lua.LState) int {
return 0 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] f, ok := p.renderCallbacks[name]
if !ok { if !ok {
return nil return nil
@ -87,7 +87,7 @@ func (p *luaPlugin) inject(name string, data koushin.RenderData) error {
return nil 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 { if err := p.inject("*", data); err != nil {
return err return err
} }
@ -144,8 +144,8 @@ func loadLuaPlugin(filename string) (*luaPlugin, error) {
filters: make(template.FuncMap), filters: make(template.FuncMap),
} }
mt := l.NewTypeMetatable("koushin") mt := l.NewTypeMetatable("alps")
l.SetGlobal("koushin", mt) l.SetGlobal("alps", mt)
l.SetField(mt, "on_render", l.NewFunction(p.onRender)) l.SetField(mt, "on_render", l.NewFunction(p.onRender))
l.SetField(mt, "set_filter", l.NewFunction(p.setFilter)) l.SetField(mt, "set_filter", l.NewFunction(p.setFilter))
l.SetField(mt, "set_route", l.NewFunction(p.setRoute)) l.SetField(mt, "set_route", l.NewFunction(p.setRoute))
@ -158,15 +158,15 @@ func loadLuaPlugin(filename string) (*luaPlugin, error) {
return p, nil return p, nil
} }
func loadAllLuaPlugins(s *koushin.Server) ([]koushin.Plugin, error) { func loadAllLuaPlugins(s *alps.Server) ([]alps.Plugin, error) {
log := s.Logger() log := s.Logger()
filenames, err := filepath.Glob(koushin.PluginDir + "/*/main.lua") filenames, err := filepath.Glob(alps.PluginDir + "/*/main.lua")
if err != nil { if err != nil {
return nil, fmt.Errorf("filepath.Glob failed: %v", err) 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 { for _, filename := range filenames {
log.Printf("Loading Lua plugin %q", filename) log.Printf("Loading Lua plugin %q", filename)

View File

@ -1,9 +1,9 @@
package koushinlua package alpslua
import ( import (
"git.sr.ht/~emersion/koushin" "git.sr.ht/~emersion/alps"
) )
func init() { func init() {
koushin.RegisterPluginLoader(loadAllLuaPlugins) alps.RegisterPluginLoader(loadAllLuaPlugins)
} }

View File

@ -1,4 +1,4 @@
package koushinviewhtml package alpsviewhtml
import ( import (
"io" "io"
@ -8,8 +8,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"git.sr.ht/~emersion/koushin" "git.sr.ht/~emersion/alps"
koushinbase "git.sr.ht/~emersion/koushin/plugins/base" alpsbase "git.sr.ht/~emersion/alps/plugins/base"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -19,10 +19,10 @@ var (
) )
func init() { func init() {
p := koushin.GoPlugin{Name: "viewhtml"} p := alps.GoPlugin{Name: "viewhtml"}
p.Inject("message.html", func(ctx *koushin.Context, _data koushin.RenderData) error { p.Inject("message.html", func(ctx *alps.Context, _data alps.RenderData) error {
data := _data.(*koushinbase.MessageRenderData) data := _data.(*alpsbase.MessageRenderData)
data.Extra["RemoteResourcesAllowed"] = ctx.QueryParam("allow-remote-resources") == "1" data.Extra["RemoteResourcesAllowed"] = ctx.QueryParam("allow-remote-resources") == "1"
hasRemoteResources := false hasRemoteResources := false
if v := ctx.Get("viewhtml.hasRemoteResources"); v != nil { if v := ctx.Get("viewhtml.hasRemoteResources"); v != nil {
@ -32,7 +32,7 @@ func init() {
return nil return nil
}) })
p.GET("/proxy", func(ctx *koushin.Context) error { p.GET("/proxy", func(ctx *alps.Context) error {
if !proxyEnabled { if !proxyEnabled {
return echo.NewHTTPError(http.StatusForbidden, "proxy disabled") return echo.NewHTTPError(http.StatusForbidden, "proxy disabled")
} }
@ -67,5 +67,5 @@ func init() {
return ctx.Stream(http.StatusOK, mediaType, &lr) return ctx.Stream(http.StatusOK, mediaType, &lr)
}) })
koushin.RegisterPluginLoader(p.Loader()) alps.RegisterPluginLoader(p.Loader())
} }

View File

@ -1,4 +1,4 @@
package koushinviewhtml package alpsviewhtml
import ( import (
"bytes" "bytes"
@ -7,7 +7,7 @@ import (
"regexp" "regexp"
"strings" "strings"
koushinbase "git.sr.ht/~emersion/koushin/plugins/base" alpsbase "git.sr.ht/~emersion/alps/plugins/base"
"github.com/aymerick/douceur/css" "github.com/aymerick/douceur/css"
cssparser "github.com/chris-ramon/douceur/parser" cssparser "github.com/chris-ramon/douceur/parser"
"github.com/microcosm-cc/bluemonday" "github.com/microcosm-cc/bluemonday"
@ -71,7 +71,7 @@ var allowedStyles = map[string]bool{
} }
type sanitizer struct { type sanitizer struct {
msg *koushinbase.IMAPMessage msg *alpsbase.IMAPMessage
allowRemoteResources bool allowRemoteResources bool
hasRemoteResources bool hasRemoteResources bool
} }

View File

@ -1,4 +1,4 @@
package koushinviewhtml package alpsviewhtml
import ( import (
"bytes" "bytes"
@ -7,8 +7,8 @@ import (
"io/ioutil" "io/ioutil"
"strings" "strings"
"git.sr.ht/~emersion/koushin" "git.sr.ht/~emersion/alps"
koushinbase "git.sr.ht/~emersion/koushin/plugins/base" alpsbase "git.sr.ht/~emersion/alps/plugins/base"
"github.com/emersion/go-message" "github.com/emersion/go-message"
) )
@ -24,7 +24,7 @@ var tpl = template.Must(template.New("view-html.html").Parse(tplSrc))
type viewer struct{} 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" allowRemoteResources := ctx.QueryParam("allow-remote-resources") == "1"
mimeType, _, err := part.Header.ContentType() mimeType, _, err := part.Header.ContentType()
@ -32,7 +32,7 @@ func (viewer) ViewMessagePart(ctx *koushin.Context, msg *koushinbase.IMAPMessage
return nil, err return nil, err
} }
if !strings.EqualFold(mimeType, "text/html") { if !strings.EqualFold(mimeType, "text/html") {
return nil, koushinbase.ErrViewUnsupported return nil, alpsbase.ErrViewUnsupported
} }
body, err := ioutil.ReadAll(part.Body) body, err := ioutil.ReadAll(part.Body)
@ -61,5 +61,5 @@ func (viewer) ViewMessagePart(ctx *koushin.Context, msg *koushinbase.IMAPMessage
} }
func init() { func init() {
koushinbase.RegisterViewer(viewer{}) alpsbase.RegisterViewer(viewer{})
} }

View File

@ -1,10 +1,10 @@
package koushinviewtext package alpsviewtext
import ( import (
"git.sr.ht/~emersion/koushin" "git.sr.ht/~emersion/alps"
) )
func init() { func init() {
p := koushin.GoPlugin{Name: "viewtext"} p := alps.GoPlugin{Name: "viewtext"}
koushin.RegisterPluginLoader(p.Loader()) alps.RegisterPluginLoader(p.Loader())
} }

View File

@ -1,4 +1,4 @@
package koushinviewtext package alpsviewtext
import ( import (
"bufio" "bufio"
@ -7,8 +7,8 @@ import (
"net/url" "net/url"
"strings" "strings"
"git.sr.ht/~emersion/koushin" "git.sr.ht/~emersion/alps"
koushinbase "git.sr.ht/~emersion/koushin/plugins/base" alpsbase "git.sr.ht/~emersion/alps/plugins/base"
"github.com/emersion/go-message" "github.com/emersion/go-message"
"gitlab.com/golang-commonmark/linkify" "gitlab.com/golang-commonmark/linkify"
) )
@ -53,13 +53,13 @@ func executeTemplate(name string, data interface{}) (template.HTML, error) {
type viewer struct{} 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() mimeType, _, err := part.Header.ContentType()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !strings.EqualFold(mimeType, "text/plain") { if !strings.EqualFold(mimeType, "text/plain") {
return nil, koushinbase.ErrViewUnsupported return nil, alpsbase.ErrViewUnsupported
} }
var tokens []interface{} var tokens []interface{}
@ -114,5 +114,5 @@ func (viewer) ViewMessagePart(ctx *koushin.Context, msg *koushinbase.IMAPMessage
} }
func init() { func init() {
koushinbase.RegisterViewer(viewer{}) alpsbase.RegisterViewer(viewer{})
} }

View File

@ -1,4 +1,4 @@
package koushin package alps
import ( import (
"fmt" "fmt"
@ -60,7 +60,7 @@ type RenderData interface {
// } // }
// //
// data := &MyRenderData{ // data := &MyRenderData{
// BaseRenderData: *koushin.NewBaseRenderData(ctx), // BaseRenderData: *alps.NewBaseRenderData(ctx),
// // other fields... // // other fields...
// } // }
func NewBaseRenderData(ctx *Context) *BaseRenderData { func NewBaseRenderData(ctx *Context) *BaseRenderData {

View File

@ -1,4 +1,4 @@
package koushin package alps
import ( import (
"fmt" "fmt"
@ -11,9 +11,9 @@ import (
"github.com/labstack/echo/v4" "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 { type Server struct {
e *echo.Echo e *echo.Echo
Sessions *SessionManager Sessions *SessionManager
@ -237,7 +237,7 @@ func (s *Server) Logger() echo.Logger {
// //
// Use a type assertion to get it from a echo.Context: // Use a type assertion to get it from a echo.Context:
// //
// ctx := ectx.(*koushin.Context) // ctx := ectx.(*alps.Context)
type Context struct { type Context struct {
echo.Context echo.Context
Server *Server Server *Server

View File

@ -1,4 +1,4 @@
package koushin package alps
import ( import (
"crypto/rand" "crypto/rand"

View File

@ -1,4 +1,4 @@
package koushin package alps
import ( import (
"fmt" "fmt"

View File

@ -1,4 +1,4 @@
package koushin package alps
import ( import (
"encoding/json" "encoding/json"
@ -12,7 +12,7 @@ import (
) )
// ErrNoStoreEntry is returned by Store.Get when the entry doesn't exist. // 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. // Store allows storing per-user persistent data.
// //
@ -72,14 +72,14 @@ type imapStore struct {
cache *memoryStore 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) { func newIMAPStore(session *Session) (*imapStore, error) {
err := session.DoIMAP(func(c *imapclient.Client) error { err := session.DoIMAP(func(c *imapclient.Client) error {
mc := imapmetadata.NewClient(c) mc := imapmetadata.NewClient(c)
ok, err := mc.SupportMetadata() ok, err := mc.SupportMetadata()
if err != nil { 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 { if !ok {
return errIMAPMetadataUnsupported return errIMAPMetadataUnsupported
@ -93,7 +93,7 @@ func newIMAPStore(session *Session) (*imapStore, error) {
} }
func (s *imapStore) key(key string) string { 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 { func (s *imapStore) Get(key string, out interface{}) error {
@ -109,14 +109,14 @@ func (s *imapStore) Get(key string, out interface{}) error {
return err return err
}) })
if err != nil { 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)] v, ok := entries[s.key(key)]
if !ok { if !ok {
return ErrNoStoreEntry return ErrNoStoreEntry
} }
if err := json.Unmarshal([]byte(v), out); err != nil { 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) 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 { func (s *imapStore) Put(key string, v interface{}) error {
b, err := json.Marshal(v) b, err := json.Marshal(v)
if err != nil { 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{ entries := map[string]string{
s.key(key): string(b), s.key(key): string(b),
@ -134,7 +134,7 @@ func (s *imapStore) Put(key string, v interface{}) error {
return mc.SetMetadata("", entries) return mc.SetMetadata("", entries)
}) })
if err != nil { 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) return s.cache.Put(key, v)

View File

@ -1,5 +1,5 @@
{{template "head.html"}} {{template "head.html"}}
<h1>koushin webmail</h1> <h1>alps webmail</h1>
<form method="post" action="/login"> <form method="post" action="/login">
<p> <p>

View File

@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff"> <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="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" /> <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> </head>

View File

@ -1,7 +1,7 @@
<nav class="container-fluid navbar navbar-light navbar-expand-sm"> <nav class="container-fluid navbar navbar-light navbar-expand-sm">
<!-- TODO: show active plugin name --> <!-- TODO: show active plugin name -->
<a class="navbar-brand" href="/"> <a class="navbar-brand" href="/">
koushin alps
<span class="text-danger">mail</span> <span class="text-danger">mail</span>
</a> </a>
{{if .LoggedIn}} {{if .LoggedIn}}