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.
|
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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package koushin
|
package alps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
2
go.mod
|
@ -1,4 +1,4 @@
|
||||||
module git.sr.ht/~emersion/koushin
|
module git.sr.ht/~emersion/alps
|
||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
|
|
2
imap.go
2
imap.go
|
@ -1,4 +1,4 @@
|
||||||
package koushin
|
package alps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package koushinbase
|
package alpsbase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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}}">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package koushinbase
|
package alpsbase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package koushinbase
|
package alpsbase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package koushinbase
|
package alpsbase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
})
|
})
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package koushin
|
package alps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
|
2
smtp.go
2
smtp.go
|
@ -1,4 +1,4 @@
|
||||||
package koushin
|
package alps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
18
store.go
18
store.go
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
Loading…
Reference in a new issue