diff --git a/.gitignore b/.gitignore index 6221491..c36e863 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ !/plugins/caldav !/plugins/carddav !/plugins/lua +!/plugins/viewhtml +!/plugins/viewtext diff --git a/cmd/koushin/main.go b/cmd/koushin/main.go index 392150f..d260d99 100644 --- a/cmd/koushin/main.go +++ b/cmd/koushin/main.go @@ -16,6 +16,8 @@ import ( _ "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" ) func main() { diff --git a/plugins/base/public/foot.html b/plugins/base/public/foot.html index 284d779..b605728 100644 --- a/plugins/base/public/foot.html +++ b/plugins/base/public/foot.html @@ -1,3 +1,2 @@ - diff --git a/plugins/base/public/head.html b/plugins/base/public/head.html index bed1bb3..bb47cfc 100644 --- a/plugins/base/public/head.html +++ b/plugins/base/public/head.html @@ -3,6 +3,5 @@ koushin - diff --git a/plugins/base/public/message.html b/plugins/base/public/message.html index a973881..5457627 100644 --- a/plugins/base/public/message.html +++ b/plugins/base/public/message.html @@ -110,7 +110,7 @@
-{{if .Body}} +{{if .View}}

{{if .Message.HasFlag "\\Draft"}} Edit draft @@ -118,13 +118,7 @@ Reply {{end}}

- {{if .IsHTML}} - - - - {{else}} -
{{.Body}}
- {{end}} + {{.View}} {{else}}

Can't preview this message part.

Download diff --git a/plugins/base/routes.go b/plugins/base/routes.go index ec22d1b..ad4d121 100644 --- a/plugins/base/routes.go +++ b/plugins/base/routes.go @@ -176,8 +176,7 @@ type MessageRenderData struct { Mailboxes []*imap.MailboxInfo Mailbox *imap.MailboxStatus Message *IMAPMessage - Body string - IsHTML bool + View interface{} PartPath string MailboxPage int Flags map[string]bool @@ -255,21 +254,9 @@ func handleGetPart(ctx *koushin.Context, raw bool) error { } } - var body []byte - if strings.HasPrefix(strings.ToLower(mimeType), "text/") { - body, err = ioutil.ReadAll(part.Body) - if err != nil { - return fmt.Errorf("failed to read part body: %v", err) - } - } - - isHTML := false - if strings.EqualFold(mimeType, "text/html") { - body, err = sanitizeHTML(body) - if err != nil { - return fmt.Errorf("failed to sanitize HTML part: %v", err) - } - isHTML = true + view, err := viewMessagePart(ctx, msg, part) + if err == ErrViewUnsupported { + view = nil } flags := make(map[string]bool) @@ -286,8 +273,7 @@ func handleGetPart(ctx *koushin.Context, raw bool) error { Mailboxes: mailboxes, Mailbox: mbox, Message: msg, - Body: string(body), - IsHTML: isHTML, + View: view, PartPath: partPathString, MailboxPage: int(mbox.Messages-msg.SeqNum) / messagesPerPage, Flags: flags, diff --git a/plugins/base/viewer.go b/plugins/base/viewer.go new file mode 100644 index 0000000..a76ecf9 --- /dev/null +++ b/plugins/base/viewer.go @@ -0,0 +1,38 @@ +package koushinbase + +import ( + "fmt" + + "git.sr.ht/~emersion/koushin" + "github.com/emersion/go-message" +) + +// ErrViewUnsupported is returned by Viewer.ViewMessagePart when the message +// part isn't supported. +var ErrViewUnsupported = fmt.Errorf("cannot generate message view: unsupported part") + +// Viewer is a message part viewer. +type Viewer interface { + // ViewMessagePart renders a message part. The returned value is displayed + // in a template. ErrViewUnsupported is returned if the message part isn't + // supported. + ViewMessagePart(*koushin.Context, *IMAPMessage, *message.Entity) (interface{}, error) +} + +var viewers []Viewer + +// RegisterViewer registers a message part viewer. +func RegisterViewer(viewer Viewer) { + viewers = append(viewers, viewer) +} + +func viewMessagePart(ctx *koushin.Context, msg *IMAPMessage, part *message.Entity) (interface{}, error) { + for _, viewer := range viewers { + v, err := viewer.ViewMessagePart(ctx, msg, part) + if err == ErrViewUnsupported { + continue + } + return v, err + } + return nil, ErrViewUnsupported +} diff --git a/plugins/viewhtml/plugin.go b/plugins/viewhtml/plugin.go new file mode 100644 index 0000000..f22364e --- /dev/null +++ b/plugins/viewhtml/plugin.go @@ -0,0 +1,10 @@ +package koushinviewhtml + +import ( + "git.sr.ht/~emersion/koushin" +) + +func init() { + p := koushin.GoPlugin{Name: "viewhtml"} + koushin.RegisterPluginLoader(p.Loader()) +} diff --git a/plugins/base/public/assets/script.js b/plugins/viewhtml/public/assets/script.js similarity index 100% rename from plugins/base/public/assets/script.js rename to plugins/viewhtml/public/assets/script.js diff --git a/plugins/base/public/assets/style.css b/plugins/viewhtml/public/assets/style.css similarity index 100% rename from plugins/base/public/assets/style.css rename to plugins/viewhtml/public/assets/style.css diff --git a/plugins/base/sanitize_html.go b/plugins/viewhtml/sanitize.go similarity index 99% rename from plugins/base/sanitize_html.go rename to plugins/viewhtml/sanitize.go index a82c852..8cee481 100644 --- a/plugins/base/sanitize_html.go +++ b/plugins/viewhtml/sanitize.go @@ -1,4 +1,4 @@ -package koushinbase +package koushinviewhtml import ( "bytes" diff --git a/plugins/viewhtml/viewer.go b/plugins/viewhtml/viewer.go new file mode 100644 index 0000000..9734c37 --- /dev/null +++ b/plugins/viewhtml/viewer.go @@ -0,0 +1,57 @@ +package koushinviewhtml + +import ( + "bytes" + "fmt" + "html/template" + "io/ioutil" + "strings" + + "git.sr.ht/~emersion/koushin" + koushinbase "git.sr.ht/~emersion/koushin/plugins/base" + "github.com/emersion/go-message" +) + +const tpl = ` + + + + + +` + +type viewer struct{} + +func (viewer) ViewMessagePart(ctx *koushin.Context, msg *koushinbase.IMAPMessage, part *message.Entity) (interface{}, error) { + mimeType, _, err := part.Header.ContentType() + if err != nil { + return nil, err + } + if !strings.EqualFold(mimeType, "text/html") { + return nil, koushinbase.ErrViewUnsupported + } + + body, err := ioutil.ReadAll(part.Body) + if err != nil { + return nil, fmt.Errorf("failed to read part body: %v", err) + } + + body, err = sanitizeHTML(body) + if err != nil { + return nil, fmt.Errorf("failed to sanitize HTML part: %v", err) + } + + t := template.Must(template.New("view-html.html").Parse(tpl)) + + var buf bytes.Buffer + err = t.Execute(&buf, string(body)) + if err != nil { + return nil, err + } + + return template.HTML(buf.String()), nil +} + +func init() { + koushinbase.RegisterViewer(viewer{}) +} diff --git a/plugins/viewtext/plugin.go b/plugins/viewtext/plugin.go new file mode 100644 index 0000000..c7a2bcc --- /dev/null +++ b/plugins/viewtext/plugin.go @@ -0,0 +1,10 @@ +package koushinviewtext + +import ( + "git.sr.ht/~emersion/koushin" +) + +func init() { + p := koushin.GoPlugin{Name: "viewtext"} + koushin.RegisterPluginLoader(p.Loader()) +} diff --git a/plugins/viewtext/viewer.go b/plugins/viewtext/viewer.go new file mode 100644 index 0000000..cca38a8 --- /dev/null +++ b/plugins/viewtext/viewer.go @@ -0,0 +1,49 @@ +package koushinviewtext + +import ( + "bytes" + "fmt" + "html/template" + "io/ioutil" + "strings" + + "git.sr.ht/~emersion/koushin" + koushinbase "git.sr.ht/~emersion/koushin/plugins/base" + "github.com/emersion/go-message" +) + +// TODO: dim quotes and "On xxx, xxx wrote:" lines +// TODO: turn URLs into links + +const tpl = `
{{.}}
` + +type viewer struct{} + +func (viewer) ViewMessagePart(ctx *koushin.Context, msg *koushinbase.IMAPMessage, part *message.Entity) (interface{}, error) { + mimeType, _, err := part.Header.ContentType() + if err != nil { + return nil, err + } + if !strings.EqualFold(mimeType, "text/plain") { + return nil, koushinbase.ErrViewUnsupported + } + + body, err := ioutil.ReadAll(part.Body) + if err != nil { + return nil, fmt.Errorf("failed to read part body: %v", err) + } + + t := template.Must(template.New("view-text.html").Parse(tpl)) + + var buf bytes.Buffer + err = t.Execute(&buf, string(body)) + if err != nil { + return nil, err + } + + return template.HTML(buf.String()), nil +} + +func init() { + koushinbase.RegisterViewer(viewer{}) +} diff --git a/themes/sourcehut/message.html b/themes/sourcehut/message.html index 4e1ab44..fb38932 100644 --- a/themes/sourcehut/message.html +++ b/themes/sourcehut/message.html @@ -99,16 +99,8 @@ {{end}} - {{if .Body}} - {{if .IsHTML}} - - - - {{else}} -
{{.Body}}
- {{end}} + {{if .View}} + {{.View}} {{else}}

Can't preview this message part.

Download