소스 검색

Turn message part viewers into plugins

master
Simon Ser 4 년 전
부모
커밋
8299617ebc
No known key found for this signature in database GPG 키 ID: FDE7BE0E88F5E48
15개의 변경된 파일178개의 추가작업 그리고 40개의 파일을 삭제
  1. +2
    -0
      .gitignore
  2. +2
    -0
      cmd/koushin/main.go
  3. +0
    -1
      plugins/base/public/foot.html
  4. +0
    -1
      plugins/base/public/head.html
  5. +2
    -8
      plugins/base/public/message.html
  6. +5
    -19
      plugins/base/routes.go
  7. +38
    -0
      plugins/base/viewer.go
  8. +10
    -0
      plugins/viewhtml/plugin.go
  9. +0
    -0
      plugins/viewhtml/public/assets/script.js
  10. +0
    -0
      plugins/viewhtml/public/assets/style.css
  11. +1
    -1
      plugins/viewhtml/sanitize.go
  12. +57
    -0
      plugins/viewhtml/viewer.go
  13. +10
    -0
      plugins/viewtext/plugin.go
  14. +49
    -0
      plugins/viewtext/viewer.go
  15. +2
    -10
      themes/sourcehut/message.html

+ 2
- 0
.gitignore 파일 보기

@@ -6,3 +6,5 @@
!/plugins/caldav !/plugins/caldav
!/plugins/carddav !/plugins/carddav
!/plugins/lua !/plugins/lua
!/plugins/viewhtml
!/plugins/viewtext

+ 2
- 0
cmd/koushin/main.go 파일 보기

@@ -16,6 +16,8 @@ import (
_ "git.sr.ht/~emersion/koushin/plugins/caldav" _ "git.sr.ht/~emersion/koushin/plugins/caldav"
_ "git.sr.ht/~emersion/koushin/plugins/carddav" _ "git.sr.ht/~emersion/koushin/plugins/carddav"
_ "git.sr.ht/~emersion/koushin/plugins/lua" _ "git.sr.ht/~emersion/koushin/plugins/lua"
_ "git.sr.ht/~emersion/koushin/plugins/viewhtml"
_ "git.sr.ht/~emersion/koushin/plugins/viewtext"
) )


func main() { func main() {


+ 0
- 1
plugins/base/public/foot.html 파일 보기

@@ -1,3 +1,2 @@
<script src="/plugins/base/assets/script.js"></script>
</body> </body>
</html> </html>

+ 0
- 1
plugins/base/public/head.html 파일 보기

@@ -3,6 +3,5 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>koushin</title> <title>koushin</title>
<link rel="stylesheet" href="/plugins/base/assets/style.css">
</head> </head>
<body> <body>

+ 2
- 8
plugins/base/public/message.html 파일 보기

@@ -110,7 +110,7 @@


<hr> <hr>


{{if .Body}}
{{if .View}}
<p> <p>
{{if .Message.HasFlag "\\Draft"}} {{if .Message.HasFlag "\\Draft"}}
<a href="{{.Message.Uid}}/edit?part={{.PartPath}}">Edit draft</a> <a href="{{.Message.Uid}}/edit?part={{.PartPath}}">Edit draft</a>
@@ -118,13 +118,7 @@
<a href="{{.Message.Uid}}/reply?part={{.PartPath}}">Reply</a> <a href="{{.Message.Uid}}/reply?part={{.PartPath}}">Reply</a>
{{end}} {{end}}
</p> </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}} {{else}}
<p>Can't preview this message part.</p> <p>Can't preview this message part.</p>
<a href="{{.Message.Uid}}/raw?part={{.PartPath}}">Download</a> <a href="{{.Message.Uid}}/raw?part={{.PartPath}}">Download</a>


+ 5
- 19
plugins/base/routes.go 파일 보기

@@ -176,8 +176,7 @@ type MessageRenderData struct {
Mailboxes []*imap.MailboxInfo Mailboxes []*imap.MailboxInfo
Mailbox *imap.MailboxStatus Mailbox *imap.MailboxStatus
Message *IMAPMessage Message *IMAPMessage
Body string
IsHTML bool
View interface{}
PartPath string PartPath string
MailboxPage int MailboxPage int
Flags map[string]bool 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) flags := make(map[string]bool)
@@ -286,8 +273,7 @@ func handleGetPart(ctx *koushin.Context, raw bool) error {
Mailboxes: mailboxes, Mailboxes: mailboxes,
Mailbox: mbox, Mailbox: mbox,
Message: msg, Message: msg,
Body: string(body),
IsHTML: isHTML,
View: view,
PartPath: partPathString, PartPath: partPathString,
MailboxPage: int(mbox.Messages-msg.SeqNum) / messagesPerPage, MailboxPage: int(mbox.Messages-msg.SeqNum) / messagesPerPage,
Flags: flags, Flags: flags,


+ 38
- 0
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
}

+ 10
- 0
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())
}

plugins/base/public/assets/script.js → plugins/viewhtml/public/assets/script.js 파일 보기


plugins/base/public/assets/style.css → plugins/viewhtml/public/assets/style.css 파일 보기


plugins/base/sanitize_html.go → plugins/viewhtml/sanitize.go 파일 보기

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


import ( import (
"bytes" "bytes"

+ 57
- 0
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{})
}

+ 10
- 0
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())
}

+ 49
- 0
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{})
}

+ 2
- 10
themes/sourcehut/message.html 파일 보기

@@ -99,16 +99,8 @@
{{end}} {{end}}
</ul> </ul>


{{if .Body}}
{{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}}
{{if .View}}
{{.View}}
{{else}} {{else}}
<p>Can't preview this message part.</p> <p>Can't preview this message part.</p>
<a href="{{.Message.Uid}}/raw?part={{.PartPath}}">Download</a> <a href="{{.Message.Uid}}/raw?part={{.PartPath}}">Download</a>


불러오는 중...
취소
저장