From 7de332c2bb5cddf6591b93b16e536b845ea7ab76 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 25 Feb 2020 18:16:26 +0100 Subject: plugins/viewtext: linkify plaintext messages --- plugins/viewtext/viewer.go | 97 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 83 insertions(+), 14 deletions(-) (limited to 'plugins') diff --git a/plugins/viewtext/viewer.go b/plugins/viewtext/viewer.go index cca38a8..9a91f08 100644 --- a/plugins/viewtext/viewer.go +++ b/plugins/viewtext/viewer.go @@ -1,21 +1,55 @@ package koushinviewtext import ( - "bytes" + "bufio" "fmt" "html/template" - "io/ioutil" + "net/url" "strings" "git.sr.ht/~emersion/koushin" koushinbase "git.sr.ht/~emersion/koushin/plugins/base" "github.com/emersion/go-message" + "gitlab.com/golang-commonmark/linkify" ) // TODO: dim quotes and "On xxx, xxx wrote:" lines -// TODO: turn URLs into links -const tpl = `
{{.}}
` +const ( + tplStr = `
{{range .}}{{.}}{{end}}
` + linkTplStr = `{{.Text}}` +) + +var tpl *template.Template + +func init() { + tpl = template.Must(template.New("view-text.html").Parse(tplStr)) + template.Must(tpl.New("view-text-link.html").Parse(linkTplStr)) +} + +type linkRenderData struct { + Href string + Text string +} + +var allowedSchemes = map[string]bool{ + "http": true, + "https": true, + "mailto": true, + "ftp": true, + "sftp": true, + "ftps": true, + "tel": true, +} + +func executeTemplate(name string, data interface{}) (template.HTML, error) { + var sb strings.Builder + err := tpl.ExecuteTemplate(&sb, name, data) + if err != nil { + return "", err + } + return template.HTML(sb.String()), nil +} type viewer struct{} @@ -28,20 +62,55 @@ func (viewer) ViewMessagePart(ctx *koushin.Context, msg *koushinbase.IMAPMessage return nil, koushinbase.ErrViewUnsupported } - body, err := ioutil.ReadAll(part.Body) - if err != nil { - return nil, fmt.Errorf("failed to read part body: %v", err) - } + var tokens []interface{} + scanner := bufio.NewScanner(part.Body) + for scanner.Scan() { + l := scanner.Text() - t := template.Must(template.New("view-text.html").Parse(tpl)) + i := 0 + for _, link := range linkify.Links(l) { + href := l[link.Start:link.End] + if link.Scheme == "" { + href = "https://" + href + } else if !strings.HasPrefix(href, link.Scheme) { + href = link.Scheme + href + } - var buf bytes.Buffer - err = t.Execute(&buf, string(body)) - if err != nil { - return nil, err + u, err := url.Parse(href) + if err != nil { + continue + } + + if !allowedSchemes[u.Scheme] { + continue + } + + // TODO: redirect mailto links to the composer + + if i < link.Start { + tokens = append(tokens, l[i:link.Start]) + } + tok, err := executeTemplate("view-text-link.html", linkRenderData{ + Href: href, + Text: l[link.Start:link.End], + }) + if err != nil { + return nil, err + } + tokens = append(tokens, tok) + i = link.End + } + if i < len(l) { + tokens = append(tokens, l[i:]) + } + + tokens = append(tokens, "\n") + } + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("failed to read part body: %v", err) } - return template.HTML(buf.String()), nil + return executeTemplate("view-text.html", tokens) } func init() { -- cgit v1.2.3