Show content warning in status
[bloat] / renderer / renderer.go
1 package renderer
2
3 import (
4         "context"
5         "io"
6         "strconv"
7         "strings"
8         "text/template"
9         "time"
10
11         "mastodon"
12 )
13
14 type Renderer interface {
15         RenderErrorPage(ctx context.Context, writer io.Writer, err error)
16         RenderHomePage(ctx context.Context, writer io.Writer) (err error)
17         RenderSigninPage(ctx context.Context, writer io.Writer) (err error)
18         RenderTimelinePage(ctx context.Context, writer io.Writer, data *TimelinePageTemplateData) (err error)
19         RenderThreadPage(ctx context.Context, writer io.Writer, data *ThreadPageTemplateData) (err error)
20         RenderNotificationPage(ctx context.Context, writer io.Writer, data *NotificationPageTemplateData) (err error)
21         RenderUserPage(ctx context.Context, writer io.Writer, data *UserPageTemplateData) (err error)
22         RenderAboutPage(ctx context.Context, writer io.Writer, data *AboutPageTemplateData) (err error)
23 }
24
25 type renderer struct {
26         template *template.Template
27 }
28
29 func NewRenderer(templateGlobPattern string) (r *renderer, err error) {
30         t := template.New("default")
31         t, err = t.Funcs(template.FuncMap{
32                 "EmojiFilter":             EmojiFilter,
33                 "StatusContentFilter":     StatusContentFilter,
34                 "DisplayInteractionCount": DisplayInteractionCount,
35                 "TimeSince":               TimeSince,
36                 "FormatTimeRFC3339":       FormatTimeRFC3339,
37         }).ParseGlob(templateGlobPattern)
38         if err != nil {
39                 return
40         }
41         return &renderer{
42                 template: t,
43         }, nil
44 }
45
46 func (r *renderer) RenderErrorPage(ctx context.Context, writer io.Writer, err error) {
47         r.template.ExecuteTemplate(writer, "error.tmpl", err)
48         return
49 }
50
51 func (r *renderer) RenderHomePage(ctx context.Context, writer io.Writer) (err error) {
52         return r.template.ExecuteTemplate(writer, "homepage.tmpl", nil)
53 }
54
55 func (r *renderer) RenderSigninPage(ctx context.Context, writer io.Writer) (err error) {
56         return r.template.ExecuteTemplate(writer, "signin.tmpl", nil)
57 }
58
59 func (r *renderer) RenderTimelinePage(ctx context.Context, writer io.Writer, data *TimelinePageTemplateData) (err error) {
60         return r.template.ExecuteTemplate(writer, "timeline.tmpl", data)
61 }
62
63 func (r *renderer) RenderThreadPage(ctx context.Context, writer io.Writer, data *ThreadPageTemplateData) (err error) {
64         return r.template.ExecuteTemplate(writer, "thread.tmpl", data)
65 }
66
67 func (r *renderer) RenderNotificationPage(ctx context.Context, writer io.Writer, data *NotificationPageTemplateData) (err error) {
68         return r.template.ExecuteTemplate(writer, "notification.tmpl", data)
69 }
70
71 func (r *renderer) RenderUserPage(ctx context.Context, writer io.Writer, data *UserPageTemplateData) (err error) {
72         return r.template.ExecuteTemplate(writer, "user.tmpl", data)
73 }
74
75 func (r *renderer) RenderAboutPage(ctx context.Context, writer io.Writer, data *AboutPageTemplateData) (err error) {
76         return r.template.ExecuteTemplate(writer, "about.tmpl", data)
77 }
78
79 func EmojiFilter(content string, emojis []mastodon.Emoji) string {
80         var replacements []string
81         for _, e := range emojis {
82                 replacements = append(replacements, ":"+e.ShortCode+":", "<img class=\"status-emoji\" src=\""+e.URL+"\" alt=\""+e.ShortCode+"\" />")
83         }
84         return strings.NewReplacer(replacements...).Replace(content)
85 }
86
87 func StatusContentFilter(spoiler string, content string, emojis []mastodon.Emoji, mentions []mastodon.Mention) string {
88         if len(spoiler) > 0 {
89                 content = spoiler + "<br />" + content
90         }
91         var replacements []string
92         for _, e := range emojis {
93                 replacements = append(replacements, ":"+e.ShortCode+":", "<img class=\"status-emoji\" src=\""+e.URL+"\" alt=\""+e.ShortCode+"\" />")
94         }
95         for _, m := range mentions {
96                 replacements = append(replacements, "\""+m.URL+"\"", "\"/user/"+m.ID+"\"")
97         }
98         return strings.NewReplacer(replacements...).Replace(content)
99 }
100
101 func DisplayInteractionCount(c int64) string {
102         if c > 0 {
103                 return strconv.Itoa(int(c))
104         }
105         return ""
106 }
107
108 func TimeSince(t time.Time) string {
109         dur := time.Since(t)
110
111         s := dur.Seconds()
112         if s < 60 {
113                 return strconv.Itoa(int(s)) + "s"
114         }
115
116         m := dur.Minutes()
117         if m < 60 {
118                 return strconv.Itoa(int(m)) + "m"
119         }
120
121         h := dur.Hours()
122         if h < 24 {
123                 return strconv.Itoa(int(h)) + "h"
124         }
125
126         d := h / 24
127         if d < 30 {
128                 return strconv.Itoa(int(d)) + "d"
129         }
130
131         mo := d / 30
132         if mo < 12 {
133                 return strconv.Itoa(int(mo)) + "mo"
134         }
135
136         y := mo / 12
137         return strconv.Itoa(int(y)) + "y"
138 }
139
140 func FormatTimeRFC3339(t time.Time) string {
141         return t.Format(time.RFC3339)
142 }