aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md10
-rw-r--r--cmd/koushin/main.go33
-rw-r--r--go.mod1
-rw-r--r--server.go33
-rw-r--r--template.go23
5 files changed, 75 insertions, 25 deletions
diff --git a/README.md b/README.md
index 874ac56..33288c8 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,16 @@
go run ./cmd/koushin imaps://mail.example.org:993 smtps://mail.example.org:465
+See `-h` for more information.
+
+## Themes
+
+They should be put in `public/themes/<name>/`.
+
+Templates in `public/themes/<name>/*.html` override default templates in
+`public/*.html`. Assets in `public/themes/<name>/assets/*` are served by the
+HTTP server at `themes/<name>/assets/*`.
+
## License
MIT
diff --git a/cmd/koushin/main.go b/cmd/koushin/main.go
index d30cb85..e644884 100644
--- a/cmd/koushin/main.go
+++ b/cmd/koushin/main.go
@@ -1,28 +1,41 @@
package main
import (
+ "flag"
"fmt"
- "os"
"git.sr.ht/~emersion/koushin"
+ "github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
+ "github.com/labstack/gommon/log"
)
func main() {
- if len(os.Args) != 2 && len(os.Args) != 3 {
- fmt.Println("usage: koushin <IMAP URL> [SMTP URL]")
- return
+ var options koushin.Options
+ flag.StringVar(&options.Theme, "theme", "", "default theme")
+
+ flag.Usage = func() {
+ fmt.Fprintf(flag.CommandLine.Output(), "usage: koushin [options...] <IMAP URL> [SMTP URL]\n")
+ flag.PrintDefaults()
}
- imapURL := os.Args[1]
+ flag.Parse()
- var smtpURL string
- if len(os.Args) == 3 {
- smtpURL = os.Args[2]
+ if flag.NArg() < 1 || flag.NArg() > 2 {
+ flag.Usage()
+ return
}
- e := koushin.New(imapURL, smtpURL)
- e.Use(middleware.Logger())
+ options.IMAPURL = flag.Arg(0)
+ options.SMTPURL = flag.Arg(1)
+
+ e := echo.New()
+ if l, ok := e.Logger.(*log.Logger); ok {
+ l.SetHeader("${time_rfc3339} ${level}")
+ }
+ if err := koushin.New(e, &options); err != nil {
+ e.Logger.Fatal(err)
+ }
e.Use(middleware.Recover())
e.Logger.Fatal(e.Start(":1323"))
}
diff --git a/go.mod b/go.mod
index be3095a..1301e14 100644
--- a/go.mod
+++ b/go.mod
@@ -8,6 +8,7 @@ require (
github.com/emersion/go-sasl v0.0.0-20190817083125-240c8404624e
github.com/emersion/go-smtp v0.12.0
github.com/labstack/echo/v4 v4.1.11
+ github.com/labstack/gommon v0.3.0
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-isatty v0.0.10 // indirect
github.com/valyala/fasttemplate v1.1.0 // indirect
diff --git a/server.go b/server.go
index 021ecff..d9542b5 100644
--- a/server.go
+++ b/server.go
@@ -79,7 +79,7 @@ func (s *Server) parseSMTPURL(smtpURL string) error {
return nil
}
-func NewServer(imapURL, smtpURL string) (*Server, error) {
+func newServer(imapURL, smtpURL string) (*Server, error) {
s := &Server{}
if err := s.parseIMAPURL(imapURL); err != nil {
@@ -310,12 +310,25 @@ func handleCompose(ectx echo.Context) error {
})
}
-func New(imapURL, smtpURL string) *echo.Echo {
- e := echo.New()
+func isPublic(path string) bool {
+ return path == "/login" || strings.HasPrefix(path, "/assets/") ||
+ strings.HasPrefix(path, "/themes/")
+}
+
+type Options struct {
+ IMAPURL, SMTPURL string
+ Theme string
+}
- s, err := NewServer(imapURL, smtpURL)
+func New(e *echo.Echo, options *Options) error {
+ s, err := newServer(options.IMAPURL, options.SMTPURL)
if err != nil {
- e.Logger.Fatal(err)
+ return err
+ }
+
+ e.Renderer, err = loadTemplates(e.Logger, options.Theme)
+ if err != nil {
+ return fmt.Errorf("failed to load templates: %v", err)
}
e.HTTPErrorHandler = func(err error, c echo.Context) {
@@ -336,7 +349,7 @@ func New(imapURL, smtpURL string) *echo.Echo {
cookie, err := ctx.Cookie(cookieName)
if err == http.ErrNoCookie {
// Require auth for all pages except /login
- if ctx.Path() == "/login" || strings.HasPrefix(ctx.Path(), "/assets/") {
+ if isPublic(ctx.Path()) {
return next(ctx)
} else {
return ctx.Redirect(http.StatusFound, "/login")
@@ -357,11 +370,6 @@ func New(imapURL, smtpURL string) *echo.Echo {
}
})
- e.Renderer, err = loadTemplates()
- if err != nil {
- e.Logger.Fatal("Failed to load templates:", err)
- }
-
e.GET("/mailbox/:mbox", func(ectx echo.Context) error {
ctx := ectx.(*context)
@@ -446,6 +454,7 @@ func New(imapURL, smtpURL string) *echo.Echo {
e.POST("/message/:mbox/:uid/reply", handleCompose)
e.Static("/assets", "public/assets")
+ e.Static("/themes", "public/themes")
- return e
+ return nil
}
diff --git a/template.go b/template.go
index 2581da0..f12e2ec 100644
--- a/template.go
+++ b/template.go
@@ -9,6 +9,7 @@ import (
)
type tmpl struct {
+ // TODO: add support for multiple themes
t *template.Template
}
@@ -16,8 +17,8 @@ func (t *tmpl) Render(w io.Writer, name string, data interface{}, c echo.Context
return t.t.ExecuteTemplate(w, name, data)
}
-func loadTemplates() (*tmpl, error) {
- t, err := template.New("drmdb").Funcs(template.FuncMap{
+func loadTemplates(logger echo.Logger, themeName string) (*tmpl, error) {
+ base, err := template.New("").Funcs(template.FuncMap{
"tuple": func(values ...interface{}) []interface{} {
return values
},
@@ -25,5 +26,21 @@ func loadTemplates() (*tmpl, error) {
return url.PathEscape(s)
},
}).ParseGlob("public/*.html")
- return &tmpl{t}, err
+ if err != nil {
+ return nil, err
+ }
+
+ theme, err := base.Clone()
+ if err != nil {
+ return nil, err
+ }
+
+ if themeName != "" {
+ logger.Printf("Loading theme \"%s\"", themeName)
+ if _, err := theme.ParseGlob("public/themes/" + themeName + "/*.html"); err != nil {
+ return nil, err
+ }
+ }
+
+ return &tmpl{theme}, err
}