Browse Source

Rename project to alps

master
Simon Ser 4 years ago
parent
commit
b891a95fcf
No known key found for this signature in database GPG Key ID: FDE7BE0E88F5E48
52 changed files with 218 additions and 218 deletions
  1. +8
    -8
      README.md
  2. +10
    -10
      cmd/alps/main.go
  3. +2
    -2
      contrib/hotreload.sh
  4. +1
    -1
      discover.go
  5. +4
    -4
      docs/cli.md
  6. +9
    -9
      docs/example-go-plugin/plugin.go
  7. +3
    -3
      docs/example-lua-plugin/main.lua
  8. +6
    -6
      docs/google.md
  9. +4
    -4
      docs/themes-and-plugins.md
  10. +1
    -1
      go.mod
  11. +1
    -1
      imap.go
  12. +2
    -2
      plugin.go
  13. +2
    -2
      plugin_go.go
  14. +1
    -1
      plugins/base/imap.go
  15. +4
    -4
      plugins/base/plugin.go
  16. +1
    -1
      plugins/base/public/compose.html
  17. +1
    -1
      plugins/base/public/head.html
  18. +1
    -1
      plugins/base/public/login.html
  19. +1
    -1
      plugins/base/public/mailbox.html
  20. +1
    -1
      plugins/base/public/message.html
  21. +1
    -1
      plugins/base/public/settings.html
  22. +34
    -34
      plugins/base/routes.go
  23. +1
    -1
      plugins/base/smtp.go
  24. +1
    -1
      plugins/base/strconv.go
  25. +1
    -1
      plugins/base/template.go
  26. +4
    -4
      plugins/base/viewer.go
  27. +5
    -5
      plugins/caldav/caldav.go
  28. +7
    -7
      plugins/caldav/plugin.go
  29. +1
    -1
      plugins/caldav/public/calendar.html
  30. +1
    -1
      plugins/caldav/public/event.html
  31. +9
    -9
      plugins/caldav/routes.go
  32. +4
    -4
      plugins/carddav/carddav.go
  33. +13
    -13
      plugins/carddav/plugin.go
  34. +1
    -1
      plugins/carddav/public/address-book.html
  35. +1
    -1
      plugins/carddav/public/address-object.html
  36. +1
    -1
      plugins/carddav/public/update-address-object.html
  37. +11
    -11
      plugins/carddav/routes.go
  38. +9
    -9
      plugins/lua/lua.go
  39. +3
    -3
      plugins/lua/plugin.go
  40. +8
    -8
      plugins/viewhtml/plugin.go
  41. +3
    -3
      plugins/viewhtml/sanitize.go
  42. +6
    -6
      plugins/viewhtml/viewer.go
  43. +4
    -4
      plugins/viewtext/plugin.go
  44. +6
    -6
      plugins/viewtext/viewer.go
  45. +2
    -2
      renderer.go
  46. +4
    -4
      server.go
  47. +1
    -1
      session.go
  48. +1
    -1
      smtp.go
  49. +9
    -9
      store.go
  50. +1
    -1
      themes/alps/login.html
  51. +1
    -1
      themes/sourcehut/head.html
  52. +1
    -1
      themes/sourcehut/nav.html

+ 8
- 8
README.md View File

@@ -1,6 +1,6 @@
# koushin
# alps


[![GoDoc](https://godoc.org/git.sr.ht/~emersion/koushin?status.svg)](https://godoc.org/git.sr.ht/~emersion/koushin)
[![GoDoc](https://godoc.org/git.sr.ht/~emersion/alps?status.svg)](https://godoc.org/git.sr.ht/~emersion/alps)


A simple and extensible webmail. A simple and extensible webmail.


@@ -8,17 +8,17 @@ A simple and extensible webmail.


Assuming SRV DNS records are properly set up (see [RFC 6186]): Assuming SRV DNS records are properly set up (see [RFC 6186]):


go run ./cmd/koushin example.org
go run ./cmd/alps example.org


To manually specify upstream servers: To manually specify upstream servers:


go run ./cmd/koushin imaps://mail.example.org:993 smtps://mail.example.org:465
go run ./cmd/alps imaps://mail.example.org:993 smtps://mail.example.org:465


Add `-theme sourcehut` to use the SourceHut theme. See `docs/cli.md` for more Add `-theme sourcehut` to use the SourceHut theme. See `docs/cli.md` for more
information. information.


When developing themes and plugins, the script `contrib/hotreload.sh` can be When developing themes and plugins, the script `contrib/hotreload.sh` can be
used to automatically reload koushin on file changes.
used to automatically reload alps on file changes.


## Contributing ## Contributing


@@ -29,6 +29,6 @@ Send patches on the [mailing list], report bugs on the [issue tracker].
MIT MIT


[RFC 6186]: https://tools.ietf.org/html/rfc6186 [RFC 6186]: https://tools.ietf.org/html/rfc6186
[Go plugin helpers]: https://godoc.org/git.sr.ht/~emersion/koushin#GoPlugin
[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

cmd/koushin/main.go → cmd/alps/main.go View File

@@ -7,28 +7,28 @@ import (
"os/signal" "os/signal"
"syscall" "syscall"


"git.sr.ht/~emersion/koushin"
"git.sr.ht/~emersion/alps"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware" "github.com/labstack/echo/v4/middleware"
"github.com/labstack/gommon/log" "github.com/labstack/gommon/log"


_ "git.sr.ht/~emersion/koushin/plugins/base"
_ "git.sr.ht/~emersion/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() { 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)
} }

+ 2
- 2
contrib/hotreload.sh View File

@@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh


# Watch themes and plugins files, automatically reload koushin on change.
# Watch themes and plugins files, automatically reload alps on change.


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

+ 1
- 1
discover.go View File

@@ -1,4 +1,4 @@
package koushin
package alps


import ( import (
"fmt" "fmt"


+ 4
- 4
docs/cli.md View File

@@ -1,22 +1,22 @@
# SYNOPSIS # SYNOPSIS


koushin [options...] <upstream servers...>
alps [options...] <upstream servers...>


# DESCRIPTION # DESCRIPTION


koushin is a simple and extensible webmail. It offers a web interface for IMAP,
alps is a simple and extensible webmail. It offers a web interface for IMAP,
SMTP and other upstream servers. SMTP and other upstream servers.


At least one upstream IMAP server needs to be specified. The easiest way to do At least one upstream IMAP server needs to be specified. The easiest way to do
so is to just specify a domain name: so is to just specify a domain name:


koushin example.org
alps example.org


This assumes SRV DNS records are properly set up (see [RFC 6186]). This assumes SRV DNS records are properly set up (see [RFC 6186]).


Alternatively, one or more upstream server URLs can be specified: Alternatively, one or more upstream server URLs can be specified:


koushin imaps://mail.example.org:993 smtps://mail.example.org:465
alps imaps://mail.example.org:993 smtps://mail.example.org:465


The following URL schemes are supported: The following URL schemes are supported:




+ 9
- 9
docs/example-go-plugin/plugin.go View File

@@ -1,22 +1,22 @@
// Package exampleplugin is an example Go plugin for koushin.
// Package exampleplugin is an example Go plugin for alps.
// //
// To enable it, import this package from cmd/koushin/main.go.
// To enable it, import this package from cmd/alps/main.go.
package exampleplugin package exampleplugin


import ( import (
"fmt" "fmt"
"net/http" "net/http"


"git.sr.ht/~emersion/koushin"
koushinbase "git.sr.ht/~emersion/koushin/plugins/base"
"git.sr.ht/~emersion/alps"
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 {
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") 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())
} }

+ 3
- 3
docs/example-lua-plugin/main.lua View File

@@ -2,18 +2,18 @@
print("Hi, this is an example Lua plugin") print("Hi, this is an example Lua plugin")


-- Setup a function called when the mailbox view is rendered -- Setup a function called when the mailbox view is rendered
koushin.on_render("mailbox.html", function(data)
alps.on_render("mailbox.html", function(data)
print("The mailbox view for " .. data.Mailbox.Name .. " is being rendered") print("The mailbox view for " .. data.Mailbox.Name .. " is being rendered")
-- Set extra data that can be accessed from the mailbox.html template -- Set extra data that can be accessed from the mailbox.html template
data.Extra.Example = "Hi from Lua" data.Extra.Example = "Hi from Lua"
end) end)


-- Wire up a new route -- Wire up a new route
koushin.set_route("GET", "/example", function(ctx)
alps.set_route("GET", "/example", function(ctx)
ctx:String(200, "This is an example page.") ctx:String(200, "This is an example page.")
end) end)


-- Set a filter function that can be used from templates -- Set a filter function that can be used from templates
koushin.set_filter("example_and", function(a, b)
alps.set_filter("example_and", function(a, b)
return a .. " and " .. b return a .. " and " .. b
end) end)

+ 6
- 6
docs/google.md View File

@@ -1,21 +1,21 @@
# Running koushin with a Google account
# Running alps with a Google account


## Create an application password ## Create an application password


First, you'll need to obtain an application-specific password for koushin from
First, you'll need to obtain an application-specific password for alps from
the [app passwords] page on your Google account. the [app passwords] page on your Google account.


## Run koushin
## Run alps


Start koushin with these upstream URLs:
Start alps with these upstream URLs:


koushin imaps://imap.gmail.com smtps://smtp.gmail.com \
alps imaps://imap.gmail.com smtps://smtp.gmail.com \
carddavs://www.googleapis.com/carddav/v1/principals/YOUREMAIL/ \ carddavs://www.googleapis.com/carddav/v1/principals/YOUREMAIL/ \
caldavs://www.google.com/calendar/dav caldavs://www.google.com/calendar/dav


Replace `YOUREMAIL` with your Google account's e-mail address. Replace `YOUREMAIL` with your Google account's e-mail address.


Once koushin is started, you can login with your e-mail address and the app
Once alps is started, you can login with your e-mail address and the app
password. password.


[app passwords]: https://security.google.com/settings/security/apppasswords [app passwords]: https://security.google.com/settings/security/apppasswords

+ 4
- 4
docs/themes-and-plugins.md View File

@@ -17,7 +17,7 @@ Assets in `plugins/<name>/public/assets/*` are served by the HTTP server at
## Go plugins ## Go plugins


They can use the [Go plugin helpers] and need to be included at compile-time in They can use the [Go plugin helpers] and need to be included at compile-time in
`cmd/koushin/main.go`.
`cmd/alps/main.go`.


## Lua plugins ## Lua plugins


@@ -25,8 +25,8 @@ The entry point is at `plugins/<name>/main.lua`.


API: API:


* `koushin.on_render(name, f)`: prior to rendering the template `name`, call
* `alps.on_render(name, f)`: prior to rendering the template `name`, call
`f` with the template data (the special name `*` matches all templates) `f` with the template data (the special name `*` matches all templates)
* `koushin.set_filter(name, f)`: set a template function
* `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 called with the HTTP context

+ 1
- 1
go.mod View File

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


go 1.13 go 1.13




+ 1
- 1
imap.go View File

@@ -1,4 +1,4 @@
package koushin
package alps


import ( import (
"fmt" "fmt"


+ 2
- 2
plugin.go View File

@@ -1,4 +1,4 @@
package koushin
package alps


import ( import (
"html/template" "html/template"
@@ -9,7 +9,7 @@ import (
// PluginDir is the path to the plugins directory. // PluginDir is the path to the plugins directory.
const PluginDir = "plugins" const PluginDir = "plugins"


// Plugin extends koushin with additional functionality.
// Plugin extends alps with additional functionality.
type Plugin interface { type Plugin interface {
// Name should return the plugin name. // Name should return the plugin name.
Name() string Name() string


+ 2
- 2
plugin_go.go View File

@@ -1,4 +1,4 @@
package koushin
package alps


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




+ 1
- 1
plugins/base/imap.go View File

@@ -1,4 +1,4 @@
package koushinbase
package alpsbase


import ( import (
"bufio" "bufio"


+ 4
- 4
plugins/base/plugin.go View File

@@ -1,14 +1,14 @@
package koushinbase
package alpsbase


import ( import (
"git.sr.ht/~emersion/koushin"
"git.sr.ht/~emersion/alps"
) )


func init() { func init() {
p := koushin.GoPlugin{Name: "base"}
p := alps.GoPlugin{Name: "base"}


p.TemplateFuncs(templateFuncs) p.TemplateFuncs(templateFuncs)
registerRoutes(&p) registerRoutes(&p)


koushin.RegisterPluginLoader(p.Loader())
alps.RegisterPluginLoader(p.Loader())
} }

+ 1
- 1
plugins/base/public/compose.html View File

@@ -1,6 +1,6 @@
{{template "head.html"}} {{template "head.html"}}


<h1>koushin</h1>
<h1>alps</h1>


<p> <p>
<a href="/mailbox/INBOX">Back</a> <a href="/mailbox/INBOX">Back</a>


+ 1
- 1
plugins/base/public/head.html View File

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

+ 1
- 1
plugins/base/public/login.html View File

@@ -1,6 +1,6 @@
{{template "head.html"}} {{template "head.html"}}


<h1>koushin</h1>
<h1>alps</h1>


<form method="post" action=""> <form method="post" action="">
<label for="username">Username:</label> <label for="username">Username:</label>


+ 1
- 1
plugins/base/public/mailbox.html View File

@@ -1,6 +1,6 @@
{{template "head.html"}} {{template "head.html"}}


<h1>koushin</h1>
<h1>alps</h1>


<p> <p>
<a href="/logout">Logout</a> <a href="/logout">Logout</a>


+ 1
- 1
plugins/base/public/message.html View File

@@ -1,6 +1,6 @@
{{template "head.html"}} {{template "head.html"}}


<h1>koushin</h1>
<h1>alps</h1>


<p> <p>
<a href="/mailbox/{{.Mailbox.Name | pathescape}}?page={{.MailboxPage}}"> <a href="/mailbox/{{.Mailbox.Name | pathescape}}?page={{.MailboxPage}}">


+ 1
- 1
plugins/base/public/settings.html View File

@@ -1,6 +1,6 @@
{{template "head.html"}} {{template "head.html"}}


<h1>koushin</h1>
<h1>alps</h1>


<p> <p>
<a href="/mailbox/INBOX">Back</a> <a href="/mailbox/INBOX">Back</a>


+ 34
- 34
plugins/base/routes.go View File

@@ -1,4 +1,4 @@
package koushinbase
package alpsbase


import ( import (
"bytes" "bytes"
@@ -11,7 +11,7 @@ import (
"strconv" "strconv"
"strings" "strings"


"git.sr.ht/~emersion/koushin"
"git.sr.ht/~emersion/alps"
"github.com/emersion/go-imap" "github.com/emersion/go-imap"
imapmove "github.com/emersion/go-imap-move" imapmove "github.com/emersion/go-imap-move"
imapclient "github.com/emersion/go-imap/client" imapclient "github.com/emersion/go-imap/client"
@@ -21,18 +21,18 @@ import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )


func registerRoutes(p *koushin.GoPlugin) {
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") 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
- 1
plugins/base/smtp.go View File

@@ -1,4 +1,4 @@
package koushinbase
package alpsbase


import ( import (
"bufio" "bufio"


+ 1
- 1
plugins/base/strconv.go View File

@@ -1,4 +1,4 @@
package koushinbase
package alpsbase


import ( import (
"fmt" "fmt"


+ 1
- 1
plugins/base/template.go View File

@@ -1,4 +1,4 @@
package koushinbase
package alpsbase


import ( import (
"html/template" "html/template"


+ 4
- 4
plugins/base/viewer.go View File

@@ -1,9 +1,9 @@
package koushinbase
package alpsbase


import ( import (
"fmt" "fmt"


"git.sr.ht/~emersion/koushin"
"git.sr.ht/~emersion/alps"
"github.com/emersion/go-message" "github.com/emersion/go-message"
) )


@@ -16,7 +16,7 @@ type Viewer interface {
// ViewMessagePart renders a message part. The returned value is displayed // ViewMessagePart renders a message part. The returned value is displayed
// in a template. ErrViewUnsupported is returned if the message part isn't // in a template. ErrViewUnsupported is returned if the message part isn't
// supported. // supported.
ViewMessagePart(*koushin.Context, *IMAPMessage, *message.Entity) (interface{}, error)
ViewMessagePart(*alps.Context, *IMAPMessage, *message.Entity) (interface{}, error)
} }


var viewers []Viewer var viewers []Viewer
@@ -26,7 +26,7 @@ func RegisterViewer(viewer Viewer) {
viewers = append(viewers, viewer) viewers = append(viewers, viewer)
} }


func viewMessagePart(ctx *koushin.Context, msg *IMAPMessage, part *message.Entity) (interface{}, error) {
func viewMessagePart(ctx *alps.Context, msg *IMAPMessage, part *message.Entity) (interface{}, error) {
for _, viewer := range viewers { for _, viewer := range viewers {
v, err := viewer.ViewMessagePart(ctx, msg, part) v, err := viewer.ViewMessagePart(ctx, msg, part)
if err == ErrViewUnsupported { if err == ErrViewUnsupported {


+ 5
- 5
plugins/caldav/caldav.go View File

@@ -1,11 +1,11 @@
package koushincaldav
package alpscaldav


import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"


"git.sr.ht/~emersion/koushin"
"git.sr.ht/~emersion/alps"
"github.com/emersion/go-webdav/caldav" "github.com/emersion/go-webdav/caldav"
) )


@@ -13,7 +13,7 @@ var errNoCalendar = fmt.Errorf("caldav: no calendar found")


type authRoundTripper struct { type authRoundTripper struct {
upstream http.RoundTripper upstream http.RoundTripper
session *koushin.Session
session *alps.Session
} }


func (rt *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { func (rt *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
@@ -21,7 +21,7 @@ func (rt *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)
return rt.upstream.RoundTrip(req) return rt.upstream.RoundTrip(req)
} }


func newClient(u *url.URL, session *koushin.Session) (*caldav.Client, error) {
func newClient(u *url.URL, session *alps.Session) (*caldav.Client, error) {
rt := authRoundTripper{ rt := authRoundTripper{
upstream: http.DefaultTransport, upstream: http.DefaultTransport,
session: session, session: session,
@@ -34,7 +34,7 @@ func newClient(u *url.URL, session *koushin.Session) (*caldav.Client, error) {
return c, nil return c, nil
} }


func getCalendar(u *url.URL, session *koushin.Session) (*caldav.Client, *caldav.Calendar, error) {
func getCalendar(u *url.URL, session *alps.Session) (*caldav.Client, *caldav.Calendar, error) {
c, err := newClient(u, session) c, err := newClient(u, session)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err


+ 7
- 7
plugins/caldav/plugin.go View File

@@ -1,11 +1,11 @@
package koushincaldav
package alpscaldav


import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"


"git.sr.ht/~emersion/koushin"
"git.sr.ht/~emersion/alps"
) )


func sanityCheckURL(u *url.URL) error { func sanityCheckURL(u *url.URL) error {
@@ -27,9 +27,9 @@ func sanityCheckURL(u *url.URL) error {
return nil return nil
} }


func newPlugin(srv *koushin.Server) (koushin.Plugin, error) {
func newPlugin(srv *alps.Server) (alps.Plugin, error) {
u, err := srv.Upstream("caldavs", "caldav+insecure", "https", "http+insecure") u, err := srv.Upstream("caldavs", "caldav+insecure", "https", "http+insecure")
if _, ok := err.(*koushin.NoUpstreamError); ok {
if _, ok := err.(*alps.NoUpstreamError); ok {
return nil, nil return nil, nil
} else if err != nil { } else if err != nil {
return nil, fmt.Errorf("caldav: failed to parse upstream caldav server: %v", err) return nil, fmt.Errorf("caldav: failed to parse upstream caldav server: %v", err)
@@ -53,7 +53,7 @@ func newPlugin(srv *koushin.Server) (koushin.Plugin, error) {


srv.Logger().Printf("Configured upstream CalDAV server: %v", u) srv.Logger().Printf("Configured upstream CalDAV server: %v", u)


p := koushin.GoPlugin{Name: "caldav"}
p := alps.GoPlugin{Name: "caldav"}


registerRoutes(&p, u) registerRoutes(&p, u)


@@ -61,7 +61,7 @@ func newPlugin(srv *koushin.Server) (koushin.Plugin, error) {
} }


func init() { func init() {
koushin.RegisterPluginLoader(func(s *koushin.Server) ([]koushin.Plugin, error) {
alps.RegisterPluginLoader(func(s *alps.Server) ([]alps.Plugin, error) {
p, err := newPlugin(s) p, err := newPlugin(s)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -69,6 +69,6 @@ func init() {
if p == nil { if p == nil {
return nil, nil return nil, nil
} }
return []koushin.Plugin{p}, err
return []alps.Plugin{p}, err
}) })
} }

+ 1
- 1
plugins/caldav/public/calendar.html View File

@@ -1,6 +1,6 @@
{{template "head.html"}} {{template "head.html"}}


<h1>koushin</h1>
<h1>alps</h1>


<p> <p>
<a href="/">Back</a> <a href="/">Back</a>


+ 1
- 1
plugins/caldav/public/event.html View File

@@ -1,6 +1,6 @@
{{template "head.html"}} {{template "head.html"}}


<h1>koushin</h1>
<h1>alps</h1>


<p> <p>
<a href="/calendar">Back</a> <a href="/calendar">Back</a>


+ 9
- 9
plugins/caldav/routes.go View File

@@ -1,4 +1,4 @@
package koushincaldav
package alpscaldav


import ( import (
"fmt" "fmt"
@@ -6,12 +6,12 @@ import (
"net/url" "net/url"
"time" "time"


"git.sr.ht/~emersion/koushin"
"git.sr.ht/~emersion/alps"
"github.com/emersion/go-webdav/caldav" "github.com/emersion/go-webdav/caldav"
) )


type CalendarRenderData struct { type CalendarRenderData struct {
koushin.BaseRenderData
alps.BaseRenderData
Time time.Time Time time.Time
Calendar *caldav.Calendar Calendar *caldav.Calendar
Events []caldav.CalendarObject Events []caldav.CalendarObject
@@ -19,15 +19,15 @@ type CalendarRenderData struct {
} }


type EventRenderData struct { type EventRenderData struct {
koushin.BaseRenderData
alps.BaseRenderData
Calendar *caldav.Calendar Calendar *caldav.Calendar
Event *caldav.CalendarObject Event *caldav.CalendarObject
} }


var monthPageLayout = "2006-01" var monthPageLayout = "2006-01"


func registerRoutes(p *koushin.GoPlugin, u *url.URL) {
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 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,
}) })


+ 4
- 4
plugins/carddav/carddav.go View File

@@ -1,11 +1,11 @@
package koushincarddav
package alpscarddav


import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"


"git.sr.ht/~emersion/koushin"
"git.sr.ht/~emersion/alps"
"github.com/emersion/go-webdav/carddav" "github.com/emersion/go-webdav/carddav"
) )


@@ -13,7 +13,7 @@ var errNoAddressBook = fmt.Errorf("carddav: no address book found")


type authRoundTripper struct { type authRoundTripper struct {
upstream http.RoundTripper upstream http.RoundTripper
session *koushin.Session
session *alps.Session
} }


func (rt *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { func (rt *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
@@ -21,7 +21,7 @@ func (rt *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)
return rt.upstream.RoundTrip(req) return rt.upstream.RoundTrip(req)
} }


func newClient(u *url.URL, session *koushin.Session) (*carddav.Client, error) {
func newClient(u *url.URL, session *alps.Session) (*carddav.Client, error) {
rt := authRoundTripper{ rt := authRoundTripper{
upstream: http.DefaultTransport, upstream: http.DefaultTransport,
session: session, session: session,


+ 13
- 13
plugins/carddav/plugin.go View File

@@ -1,4 +1,4 @@
package koushincarddav
package alpscarddav


import ( import (
"fmt" "fmt"
@@ -6,8 +6,8 @@ import (
"net/url" "net/url"
"strings" "strings"


"git.sr.ht/~emersion/koushin"
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-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 {
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) 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
- 1
plugins/carddav/public/address-book.html View File

@@ -1,6 +1,6 @@
{{template "head.html"}} {{template "head.html"}}


<h1>koushin</h1>
<h1>alps</h1>


<p> <p>
<a href="/">Back</a> · <a href="/contacts/create">Create new contact</a> <a href="/">Back</a> · <a href="/contacts/create">Create new contact</a>


+ 1
- 1
plugins/carddav/public/address-object.html View File

@@ -1,6 +1,6 @@
{{template "head.html"}} {{template "head.html"}}


<h1>koushin</h1>
<h1>alps</h1>


<p> <p>
<a href="/contacts">Back</a> <a href="/contacts">Back</a>


+ 1
- 1
plugins/carddav/public/update-address-object.html View File

@@ -1,6 +1,6 @@
{{template "head.html"}} {{template "head.html"}}


<h1>koushin</h1>
<h1>alps</h1>


<p> <p>
<a href="/contacts">Back</a> <a href="/contacts">Back</a>


+ 11
- 11
plugins/carddav/routes.go View File

@@ -1,4 +1,4 @@
package koushincarddav
package alpscarddav


import ( import (
"fmt" "fmt"
@@ -7,7 +7,7 @@ import (
"path" "path"
"strings" "strings"


"git.sr.ht/~emersion/koushin"
"git.sr.ht/~emersion/alps"
"github.com/emersion/go-vcard" "github.com/emersion/go-vcard"
"github.com/emersion/go-webdav/carddav" "github.com/emersion/go-webdav/carddav"
"github.com/google/uuid" "github.com/google/uuid"
@@ -15,19 +15,19 @@ import (
) )


type AddressBookRenderData struct { type AddressBookRenderData struct {
koushin.BaseRenderData
alps.BaseRenderData
AddressBook *carddav.AddressBook AddressBook *carddav.AddressBook
AddressObjects []AddressObject AddressObjects []AddressObject
Query string Query string
} }


type AddressObjectRenderData struct { type AddressObjectRenderData struct {
koushin.BaseRenderData
alps.BaseRenderData
AddressObject AddressObject AddressObject AddressObject
} }


type UpdateAddressObjectRenderData struct { type UpdateAddressObjectRenderData struct {
koushin.BaseRenderData
alps.BaseRenderData
AddressObject *carddav.AddressObject // nil if creating a new contact AddressObject *carddav.AddressObject // nil if creating a new contact
Card vcard.Card Card vcard.Card
} }
@@ -42,7 +42,7 @@ func parseObjectPath(s string) (string, error) {
} }


func registerRoutes(p *plugin) { func registerRoutes(p *plugin) {
p.GET("/contacts", func(ctx *koushin.Context) error {
p.GET("/contacts", func(ctx *alps.Context) error {
queryText := ctx.QueryParam("query") queryText := ctx.QueryParam("query")


c, addressBook, err := p.clientWithAddressBook(ctx.Session) c, addressBook, err := p.clientWithAddressBook(ctx.Session)
@@ -82,14 +82,14 @@ func registerRoutes(p *plugin) {
} }


return ctx.Render(http.StatusOK, "address-book.html", &AddressBookRenderData{ return ctx.Render(http.StatusOK, "address-book.html", &AddressBookRenderData{
BaseRenderData: *koushin.NewBaseRenderData(ctx),
BaseRenderData: *alps.NewBaseRenderData(ctx),
AddressBook: addressBook, AddressBook: addressBook,
AddressObjects: newAddressObjectList(aos), AddressObjects: newAddressObjectList(aos),
Query: queryText, Query: queryText,
}) })
}) })


p.GET("/contacts/:path", func(ctx *koushin.Context) error {
p.GET("/contacts/:path", func(ctx *alps.Context) error {
path, err := parseObjectPath(ctx.Param("path")) path, err := parseObjectPath(ctx.Param("path"))
if err != nil { if err != nil {
return err return err
@@ -119,12 +119,12 @@ func registerRoutes(p *plugin) {
ao := &aos[0] ao := &aos[0]


return ctx.Render(http.StatusOK, "address-object.html", &AddressObjectRenderData{ return ctx.Render(http.StatusOK, "address-object.html", &AddressObjectRenderData{
BaseRenderData: *koushin.NewBaseRenderData(ctx),
BaseRenderData: *alps.NewBaseRenderData(ctx),
AddressObject: AddressObject{ao}, AddressObject: AddressObject{ao},
}) })
}) })


updateContact := func(ctx *koushin.Context) error {
updateContact := func(ctx *alps.Context) error {
addressObjectPath, err := parseObjectPath(ctx.Param("path")) addressObjectPath, err := parseObjectPath(ctx.Param("path"))
if err != nil { if err != nil {
return err return err
@@ -200,7 +200,7 @@ func registerRoutes(p *plugin) {
} }


return ctx.Render(http.StatusOK, "update-address-object.html", &UpdateAddressObjectRenderData{ return ctx.Render(http.StatusOK, "update-address-object.html", &UpdateAddressObjectRenderData{
BaseRenderData: *koushin.NewBaseRenderData(ctx),
BaseRenderData: *alps.NewBaseRenderData(ctx),
AddressObject: ao, AddressObject: ao,
Card: card, Card: card,
}) })


+ 9
- 9
plugins/lua/lua.go View File

@@ -1,11 +1,11 @@
package koushinlua
package alpslua


import ( import (
"fmt" "fmt"
"html/template" "html/template"
"path/filepath" "path/filepath"


"git.sr.ht/~emersion/koushin"
"git.sr.ht/~emersion/alps"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/yuin/gopher-lua" "github.com/yuin/gopher-lua"
"layeh.com/gopher-luar" "layeh.com/gopher-luar"
@@ -69,7 +69,7 @@ func (p *luaPlugin) setRoute(l *lua.LState) int {
return 0 return 0
} }


func (p *luaPlugin) inject(name string, data koushin.RenderData) error {
func (p *luaPlugin) inject(name string, data alps.RenderData) error {
f, ok := p.renderCallbacks[name] f, ok := p.renderCallbacks[name]
if !ok { if !ok {
return nil return nil
@@ -87,7 +87,7 @@ func (p *luaPlugin) inject(name string, data koushin.RenderData) error {
return nil return nil
} }


func (p *luaPlugin) Inject(ctx *koushin.Context, name string, data koushin.RenderData) error {
func (p *luaPlugin) Inject(ctx *alps.Context, name string, data alps.RenderData) error {
if err := p.inject("*", data); err != nil { if err := p.inject("*", data); err != nil {
return err return err
} }
@@ -144,8 +144,8 @@ func loadLuaPlugin(filename string) (*luaPlugin, error) {
filters: make(template.FuncMap), filters: make(template.FuncMap),
} }


mt := l.NewTypeMetatable("koushin")
l.SetGlobal("koushin", mt)
mt := l.NewTypeMetatable("alps")
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)




+ 3
- 3
plugins/lua/plugin.go View File

@@ -1,9 +1,9 @@
package koushinlua
package alpslua


import ( import (
"git.sr.ht/~emersion/koushin"
"git.sr.ht/~emersion/alps"
) )


func init() { func init() {
koushin.RegisterPluginLoader(loadAllLuaPlugins)
alps.RegisterPluginLoader(loadAllLuaPlugins)
} }

+ 8
- 8
plugins/viewhtml/plugin.go View File

@@ -1,4 +1,4 @@
package koushinviewhtml
package alpsviewhtml


import ( import (
"io" "io"
@@ -8,8 +8,8 @@ import (
"strconv" "strconv"
"strings" "strings"


"git.sr.ht/~emersion/koushin"
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" "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 {
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" 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())
} }

+ 3
- 3
plugins/viewhtml/sanitize.go View File

@@ -1,4 +1,4 @@
package koushinviewhtml
package alpsviewhtml


import ( import (
"bytes" "bytes"
@@ -7,7 +7,7 @@ import (
"regexp" "regexp"
"strings" "strings"


koushinbase "git.sr.ht/~emersion/koushin/plugins/base"
alpsbase "git.sr.ht/~emersion/alps/plugins/base"
"github.com/aymerick/douceur/css" "github.com/aymerick/douceur/css"
cssparser "github.com/chris-ramon/douceur/parser" cssparser "github.com/chris-ramon/douceur/parser"
"github.com/microcosm-cc/bluemonday" "github.com/microcosm-cc/bluemonday"
@@ -71,7 +71,7 @@ var allowedStyles = map[string]bool{
} }


type sanitizer struct { type sanitizer struct {
msg *koushinbase.IMAPMessage
msg *alpsbase.IMAPMessage
allowRemoteResources bool allowRemoteResources bool
hasRemoteResources bool hasRemoteResources bool
} }


+ 6
- 6
plugins/viewhtml/viewer.go View File

@@ -1,4 +1,4 @@
package koushinviewhtml
package alpsviewhtml


import ( import (
"bytes" "bytes"
@@ -7,8 +7,8 @@ import (
"io/ioutil" "io/ioutil"
"strings" "strings"


"git.sr.ht/~emersion/koushin"
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" "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{})
} }

+ 4
- 4
plugins/viewtext/plugin.go View File

@@ -1,10 +1,10 @@
package koushinviewtext
package alpsviewtext


import ( import (
"git.sr.ht/~emersion/koushin"
"git.sr.ht/~emersion/alps"
) )


func init() { func init() {
p := koushin.GoPlugin{Name: "viewtext"}
koushin.RegisterPluginLoader(p.Loader())
p := alps.GoPlugin{Name: "viewtext"}
alps.RegisterPluginLoader(p.Loader())
} }

+ 6
- 6
plugins/viewtext/viewer.go View File

@@ -1,4 +1,4 @@
package koushinviewtext
package alpsviewtext


import ( import (
"bufio" "bufio"
@@ -7,8 +7,8 @@ import (
"net/url" "net/url"
"strings" "strings"


"git.sr.ht/~emersion/koushin"
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" "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{})
} }

+ 2
- 2
renderer.go View File

@@ -1,4 +1,4 @@
package koushin
package alps


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


+ 4
- 4
server.go View File

@@ -1,4 +1,4 @@
package koushin
package alps


import ( import (
"fmt" "fmt"
@@ -11,9 +11,9 @@ import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )


const cookieName = "koushin_session"
const cookieName = "alps_session"


// Server holds all the koushin server state.
// Server holds all the alps server state.
type Server struct { type Server struct {
e *echo.Echo e *echo.Echo
Sessions *SessionManager Sessions *SessionManager
@@ -237,7 +237,7 @@ func (s *Server) Logger() echo.Logger {
// //
// Use a type assertion to get it from a echo.Context: // Use a type assertion to get it from a echo.Context:
// //
// ctx := ectx.(*koushin.Context)
// ctx := ectx.(*alps.Context)
type Context struct { type Context struct {
echo.Context echo.Context
Server *Server Server *Server


+ 1
- 1
session.go View File

@@ -1,4 +1,4 @@
package koushin
package alps


import ( import (
"crypto/rand" "crypto/rand"


+ 1
- 1
smtp.go View File

@@ -1,4 +1,4 @@
package koushin
package alps


import ( import (
"fmt" "fmt"


+ 9
- 9
store.go View File

@@ -1,4 +1,4 @@
package koushin
package alps


import ( import (
"encoding/json" "encoding/json"
@@ -12,7 +12,7 @@ import (
) )


// ErrNoStoreEntry is returned by Store.Get when the entry doesn't exist. // ErrNoStoreEntry is returned by Store.Get when the entry doesn't exist.
var ErrNoStoreEntry = fmt.Errorf("koushin: no such entry in store")
var ErrNoStoreEntry = fmt.Errorf("alps: no such entry in store")


// Store allows storing per-user persistent data. // Store allows storing per-user persistent data.
// //
@@ -72,14 +72,14 @@ type imapStore struct {
cache *memoryStore cache *memoryStore
} }


var errIMAPMetadataUnsupported = fmt.Errorf("koushin: IMAP server doesn't support METADATA extension")
var errIMAPMetadataUnsupported = fmt.Errorf("alps: IMAP server doesn't support METADATA extension")


func newIMAPStore(session *Session) (*imapStore, error) { func newIMAPStore(session *Session) (*imapStore, error) {
err := session.DoIMAP(func(c *imapclient.Client) error { err := session.DoIMAP(func(c *imapclient.Client) error {
mc := imapmetadata.NewClient(c) mc := imapmetadata.NewClient(c)
ok, err := mc.SupportMetadata() ok, err := mc.SupportMetadata()
if err != nil { if err != nil {
return fmt.Errorf("koushin: failed to check for IMAP METADATA support: %v", err)
return fmt.Errorf("alps: failed to check for IMAP METADATA support: %v", err)
} }
if !ok { if !ok {
return errIMAPMetadataUnsupported return errIMAPMetadataUnsupported
@@ -93,7 +93,7 @@ func newIMAPStore(session *Session) (*imapStore, error) {
} }


func (s *imapStore) key(key string) string { func (s *imapStore) key(key string) string {
return "/private/vendor/koushin/" + key
return "/private/vendor/alps/" + key
} }


func (s *imapStore) Get(key string, out interface{}) error { func (s *imapStore) Get(key string, out interface{}) error {
@@ -109,14 +109,14 @@ func (s *imapStore) Get(key string, out interface{}) error {
return err return err
}) })
if err != nil { if err != nil {
return fmt.Errorf("koushin: failed to fetch IMAP store entry %q: %v", key, err)
return fmt.Errorf("alps: failed to fetch IMAP store entry %q: %v", key, err)
} }
v, ok := entries[s.key(key)] v, ok := entries[s.key(key)]
if !ok { if !ok {
return ErrNoStoreEntry return ErrNoStoreEntry
} }
if err := json.Unmarshal([]byte(v), out); err != nil { if err := json.Unmarshal([]byte(v), out); err != nil {
return fmt.Errorf("koushin: failed to unmarshal IMAP store entry %q: %v", key, err)
return fmt.Errorf("alps: failed to unmarshal IMAP store entry %q: %v", key, err)
} }
return s.cache.Put(key, out) return s.cache.Put(key, out)
} }
@@ -124,7 +124,7 @@ func (s *imapStore) Get(key string, out interface{}) error {
func (s *imapStore) Put(key string, v interface{}) error { func (s *imapStore) Put(key string, v interface{}) error {
b, err := json.Marshal(v) b, err := json.Marshal(v)
if err != nil { if err != nil {
return fmt.Errorf("koushin: failed to marshal IMAP store entry %q: %v", key, err)
return fmt.Errorf("alps: failed to marshal IMAP store entry %q: %v", key, err)
} }
entries := map[string]string{ entries := map[string]string{
s.key(key): string(b), s.key(key): string(b),
@@ -134,7 +134,7 @@ func (s *imapStore) Put(key string, v interface{}) error {
return mc.SetMetadata("", entries) return mc.SetMetadata("", entries)
}) })
if err != nil { if err != nil {
return fmt.Errorf("koushin: failed to put IMAP store entry %q: %v", key, err)
return fmt.Errorf("alps: failed to put IMAP store entry %q: %v", key, err)
} }


return s.cache.Put(key, v) return s.cache.Put(key, v)


+ 1
- 1
themes/alps/login.html View File

@@ -1,5 +1,5 @@
{{template "head.html"}} {{template "head.html"}}
<h1>koushin webmail</h1>
<h1>alps webmail</h1>


<form method="post" action="/login"> <form method="post" action="/login">
<p> <p>


+ 1
- 1
themes/sourcehut/head.html View File

@@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#ffffff">
<title>koushin webmail</title>
<title>alps webmail</title>
<link rel="stylesheet" href="/themes/sourcehut/assets/style.css"> <link rel="stylesheet" href="/themes/sourcehut/assets/style.css">
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QIGCC8n92KyhQAAAj1QTFRFAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////anIwUQAAAL50Uk5TAAECAwQFBgcICQoLDA4PEBESExQVFhcYGRobHB0eHyAhIyQmJygpKistLzAzNDU2Nzg5Ozw9QEFDREZHSElLTE1OT1BRVFdYWVpbXF1eX2BhZGZnaGltbnBxdHV3eHp7fn+AgYKDhIWGh4iJio2TlJucnqGio6Smp6ipqqusrbCxsrO0tre4ury9vr/Cw8TFxsfIycrMzc7P0dLT1dbY2dvf4OLj5OXm5+jq6+zt7u/w8fL09fb3+Pn6+/z9/gNzyOkAAAABYktHRL6k3IPDAAAFwUlEQVQYGe3B+VtUVQAG4G9i0TQZZyA1S0JxydzDNFTUqXBfcylzS8UE21TMyjAQUQnFEi0BHQU3cAc0UGbm+9v65Zw7y70zc++dc3qenof3xZAhQ4b8T+V/uGn/kdrm1psdHTdbm2uP7Ns434//yLD5e+u7aOH+6T0ludBszGf1L5jC87rNBdDm9XXnw0wrfG71cOhQ9N0z2vTk20Ko9t5PYToQOj4FKr17IkKHwtUToMobVQN0of/rkVDik7t06c5yZC7/V1rqajq6f9vKQFlZYNW2A0cvdNPSL35kaPEDmgxerFiSjwQFZQebQzTpKkUmsg4xUX/Nijwk4V15aoAJIhVZcM13ngnatuQhJe/WNiY464VLE28xXsMCD9LyLDzLeMEiuDLzIeM0zIRNs88xTvcMuFDSx1htC+FA6XXG6pkLx0qeM8aLHdlwJGfnP4zRNw8OzexjjOaJcGzSn4zR8z4cmfiQUZF9WXAhuyLCqO4iOOC7xahni+BSWQ+jgl7YlnWeUR3FcG1qJ6MasmDXIUZdexMZGNvGqAOwaTGjrnqREd/fNERKYUvBAxqueZEhXzsNXX7Y8SsNHW8iY+M6afgZNnxKw7NiKDCtl4YA0nrjHqXIIiixjIY7I5FOFQ37oEglDZVIo+glpeYsKJJ9mdJAIVI7QenFRChT3E+pGim9F6G0AwrtohSeglR+otSWDYVygpSqkUJRmNJCKLWEUmgCkvuOUgPU8vxO6TCSev0ZpZlQ7ANKj4YhmXWUGqBcI6UVSOY8pQVQbhGlM0hiTJhCmwfKeW5QCPlh7TNKW6DBdkobYa2eQn8eNPC9pPAbLA17QaEGWtRS6MuBlfmUVkCLtZTmwspeCoN50MIXprATVuopXIQmLRROwUoXhQpoUkXhNizkU1oCTZZRyoPZh5Tyock4SnNgtolCF3TxPKKwBmb7KTRBm0sUdsPsCIWj0OY4hR9gVkthP7SppHASZs0UtkGbLyg0wqyVwkpos4FCC8xuUghAm3IK7TDroFAGbQIUgjDroFAGbQIUgjC7SSEAbcoptMOslcIqaLOBQgvMmilsgzY7KDTCrJbCAWhTReEkzI5QOAptfqTwPcz2UbgAbS5R2AWzjRS6oYvnMYXVMJtPqQCavEVpNsz8lMqgyXJKo2DhPoWD0OQwhU5YOU2hGZpcoVADK3sohLzQwh+h8CWslFBaCS3WUZoDK7nPKZyCFnUUerNh6TSFAS808L+iUANrmylthQafU1oPawVhCm0eKOcJUhgcjSTOUVoI5RZTqkMyqymdhXJNlMqRzPAnlGZDsRJKD3OR1LeUzkEtTxOlSiRXGKL0EZRaSmlwPFI4Tul6DhTKvUXpGFKZEqa0EwrtoRQqRkrVlP6ZBGUmD1A6htQm9FP6IxuK5Fyl1P8O0viahgoo8g0NB5HOyDuUImVQ4mMabo9AWgEaeqZCgel9NCyFDb/Q0DkWGXv7Lg0nYIe/i4Y2HzKUf4OGe6NhS2mEhr98yEh+Kw3hBbCpglHt45CB8TcY9RXsyjrLqM5pcG36XUadfg22eYOM6l0Glz7uY9T1UXCg6AFjHMqGCznfMMb9Qjgyo4cxLhfDsclXGePpdDg0r48x+nflwJHcPQOM0TsLjs3rYazgEtjnWXqLsZ7Oggszuhnn9w9gU0kT49yfDleKgozXuMiDtDyLmxjveiFc8jYwwY3tPqTk/zzIBPWj4FpWRYQJXtau9SEJ/7q6V0wQ/uo1ZKK0iybhlqplYxHP89byw1ciNLm3ABny/0xLjy4dr/xiQ3kgUL5hR9WPlx7T0onRyFzgDl26vRRKjKwcoAv9B0dAlcLqMB0KH3sHKk2pDtGBwWPFUG3C4Ue06WHleOgwbMWZENMarCvPhTb+jb/1MYXemvWjoVnO3J2nbtNCZ82Xc7LxH8mbs2b3DycbW9qDwfaWxpPf71o9exSGDBky5P/pX9F6dsCMuJp+AAAAAElFTkSuQmCC" /> <link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QIGCC8n92KyhQAAAj1QTFRFAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////anIwUQAAAL50Uk5TAAECAwQFBgcICQoLDA4PEBESExQVFhcYGRobHB0eHyAhIyQmJygpKistLzAzNDU2Nzg5Ozw9QEFDREZHSElLTE1OT1BRVFdYWVpbXF1eX2BhZGZnaGltbnBxdHV3eHp7fn+AgYKDhIWGh4iJio2TlJucnqGio6Smp6ipqqusrbCxsrO0tre4ury9vr/Cw8TFxsfIycrMzc7P0dLT1dbY2dvf4OLj5OXm5+jq6+zt7u/w8fL09fb3+Pn6+/z9/gNzyOkAAAABYktHRL6k3IPDAAAFwUlEQVQYGe3B+VtUVQAG4G9i0TQZZyA1S0JxydzDNFTUqXBfcylzS8UE21TMyjAQUQnFEi0BHQU3cAc0UGbm+9v65Zw7y70zc++dc3qenof3xZAhQ4b8T+V/uGn/kdrm1psdHTdbm2uP7Ns434//yLD5e+u7aOH+6T0ludBszGf1L5jC87rNBdDm9XXnw0wrfG71cOhQ9N0z2vTk20Ko9t5PYToQOj4FKr17IkKHwtUToMobVQN0of/rkVDik7t06c5yZC7/V1rqajq6f9vKQFlZYNW2A0cvdNPSL35kaPEDmgxerFiSjwQFZQebQzTpKkUmsg4xUX/Nijwk4V15aoAJIhVZcM13ngnatuQhJe/WNiY464VLE28xXsMCD9LyLDzLeMEiuDLzIeM0zIRNs88xTvcMuFDSx1htC+FA6XXG6pkLx0qeM8aLHdlwJGfnP4zRNw8OzexjjOaJcGzSn4zR8z4cmfiQUZF9WXAhuyLCqO4iOOC7xahni+BSWQ+jgl7YlnWeUR3FcG1qJ6MasmDXIUZdexMZGNvGqAOwaTGjrnqREd/fNERKYUvBAxqueZEhXzsNXX7Y8SsNHW8iY+M6afgZNnxKw7NiKDCtl4YA0nrjHqXIIiixjIY7I5FOFQ37oEglDZVIo+glpeYsKJJ9mdJAIVI7QenFRChT3E+pGim9F6G0AwrtohSeglR+otSWDYVygpSqkUJRmNJCKLWEUmgCkvuOUgPU8vxO6TCSev0ZpZlQ7ANKj4YhmXWUGqBcI6UVSOY8pQVQbhGlM0hiTJhCmwfKeW5QCPlh7TNKW6DBdkobYa2eQn8eNPC9pPAbLA17QaEGWtRS6MuBlfmUVkCLtZTmwspeCoN50MIXprATVuopXIQmLRROwUoXhQpoUkXhNizkU1oCTZZRyoPZh5Tyock4SnNgtolCF3TxPKKwBmb7KTRBm0sUdsPsCIWj0OY4hR9gVkthP7SppHASZs0UtkGbLyg0wqyVwkpos4FCC8xuUghAm3IK7TDroFAGbQIUgjDroFAGbQIUgjC7SSEAbcoptMOslcIqaLOBQgvMmilsgzY7KDTCrJbCAWhTReEkzI5QOAptfqTwPcz2UbgAbS5R2AWzjRS6oYvnMYXVMJtPqQCavEVpNsz8lMqgyXJKo2DhPoWD0OQwhU5YOU2hGZpcoVADK3sohLzQwh+h8CWslFBaCS3WUZoDK7nPKZyCFnUUerNh6TSFAS808L+iUANrmylthQafU1oPawVhCm0eKOcJUhgcjSTOUVoI5RZTqkMyqymdhXJNlMqRzPAnlGZDsRJKD3OR1LeUzkEtTxOlSiRXGKL0EZRaSmlwPFI4Tul6DhTKvUXpGFKZEqa0EwrtoRQqRkrVlP6ZBGUmD1A6htQm9FP6IxuK5Fyl1P8O0viahgoo8g0NB5HOyDuUImVQ4mMabo9AWgEaeqZCgel9NCyFDb/Q0DkWGXv7Lg0nYIe/i4Y2HzKUf4OGe6NhS2mEhr98yEh+Kw3hBbCpglHt45CB8TcY9RXsyjrLqM5pcG36XUadfg22eYOM6l0Glz7uY9T1UXCg6AFjHMqGCznfMMb9Qjgyo4cxLhfDsclXGePpdDg0r48x+nflwJHcPQOM0TsLjs3rYazgEtjnWXqLsZ7Oggszuhnn9w9gU0kT49yfDleKgozXuMiDtDyLmxjveiFc8jYwwY3tPqTk/zzIBPWj4FpWRYQJXtau9SEJ/7q6V0wQ/uo1ZKK0iybhlqplYxHP89byw1ciNLm3ABny/0xLjy4dr/xiQ3kgUL5hR9WPlx7T0onRyFzgDl26vRRKjKwcoAv9B0dAlcLqMB0KH3sHKk2pDtGBwWPFUG3C4Ue06WHleOgwbMWZENMarCvPhTb+jb/1MYXemvWjoVnO3J2nbtNCZ82Xc7LxH8mbs2b3DycbW9qDwfaWxpPf71o9exSGDBky5P/pX9F6dsCMuJp+AAAAAElFTkSuQmCC" />
</head> </head>


+ 1
- 1
themes/sourcehut/nav.html View File

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


Loading…
Cancel
Save