Rename project to alps
This commit is contained in:
parent
4cf5ad68af
commit
b891a95fcf
52 changed files with 218 additions and 218 deletions
16
README.md
16
README.md
|
@ -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
|
||||
|
|
2
go.mod
2
go.mod
|
@ -1,4 +1,4 @@
|
|||
module git.sr.ht/~emersion/koushin
|
||||
module git.sr.ht/~emersion/alps
|
||||
|
||||
go 1.13
|
||||
|
||||
|
|
2
imap.go
2
imap.go
|
@ -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"
|
||||
|
|
2
smtp.go
2
smtp.go
|
@ -1,4 +1,4 @@
|
|||
package koushin
|
||||
package alps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
|
18
store.go
18
store.go
|
@ -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}}
|
||||
|
|
Loading…
Reference in a new issue