aboutsummaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/base/public/foot.html1
-rw-r--r--plugins/base/public/head.html1
-rw-r--r--plugins/base/public/message.html10
-rw-r--r--plugins/base/routes.go24
-rw-r--r--plugins/base/viewer.go38
-rw-r--r--plugins/viewhtml/plugin.go10
-rw-r--r--plugins/viewhtml/public/assets/script.js (renamed from plugins/base/public/assets/script.js)0
-rw-r--r--plugins/viewhtml/public/assets/style.css (renamed from plugins/base/public/assets/style.css)0
-rw-r--r--plugins/viewhtml/sanitize.go (renamed from plugins/base/sanitize_html.go)2
-rw-r--r--plugins/viewhtml/viewer.go57
-rw-r--r--plugins/viewtext/plugin.go10
-rw-r--r--plugins/viewtext/viewer.go49
12 files changed, 172 insertions, 30 deletions
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 @@
- <script src="/plugins/base/assets/script.js"></script>
</body>
</html>
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 @@
<head>
<meta charset="utf-8">
<title>koushin</title>
- <link rel="stylesheet" href="/plugins/base/assets/style.css">
</head>
<body>
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 @@
<hr>
-{{if .Body}}
+{{if .View}}
<p>
{{if .Message.HasFlag "\\Draft"}}
<a href="{{.Message.Uid}}/edit?part={{.PartPath}}">Edit draft</a>
@@ -118,13 +118,7 @@
<a href="{{.Message.Uid}}/reply?part={{.PartPath}}">Reply</a>
{{end}}
</p>
- {{if .IsHTML}}
- <!-- allow-same-origin is required to resize the frame with its content -->
- <!-- allow-popups is required for target="_blank" links -->
- <iframe id="email-frame" srcdoc="{{.Body}}" sandbox="allow-same-origin allow-popups"></iframe>
- {{else}}
- <pre>{{.Body}}</pre>
- {{end}}
+ {{.View}}
{{else}}
<p>Can't preview this message part.</p>
<a href="{{.Message.Uid}}/raw?part={{.PartPath}}">Download</a>
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
index fd7a1d5..fd7a1d5 100644
--- a/plugins/base/public/assets/script.js
+++ b/plugins/viewhtml/public/assets/script.js
diff --git a/plugins/base/public/assets/style.css b/plugins/viewhtml/public/assets/style.css
index 4f91f63..4f91f63 100644
--- a/plugins/base/public/assets/style.css
+++ b/plugins/viewhtml/public/assets/style.css
diff --git a/plugins/base/sanitize_html.go b/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 = `
+<!-- allow-same-origin is required to resize the frame with its content -->
+<!-- allow-popups is required for target="_blank" links -->
+<iframe id="email-frame" srcdoc="{{.}}" sandbox="allow-same-origin allow-popups"></iframe>
+<script src="/plugins/viewhtml/assets/script.js"></script>
+<link rel="stylesheet" href="/plugins/viewhtml/assets/style.css">
+`
+
+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 = `<pre>{{.}}</pre>`
+
+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{})
+}