Add settings page
[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, data *ErrorData)
16         RenderHomePage(ctx context.Context, writer io.Writer, data *HomePageData) (err error)
17         RenderSigninPage(ctx context.Context, writer io.Writer, data *SigninData) (err error)
18         RenderTimelinePage(ctx context.Context, writer io.Writer, data *TimelineData) (err error)
19         RenderThreadPage(ctx context.Context, writer io.Writer, data *ThreadData) (err error)
20         RenderNotificationPage(ctx context.Context, writer io.Writer, data *NotificationData) (err error)
21         RenderUserPage(ctx context.Context, writer io.Writer, data *UserData) (err error)
22         RenderAboutPage(ctx context.Context, writer io.Writer, data *AboutData) (err error)
23         RenderEmojiPage(ctx context.Context, writer io.Writer, data *EmojiData) (err error)
24         RenderLikedByPage(ctx context.Context, writer io.Writer, data *LikedByData) (err error)
25         RenderRetweetedByPage(ctx context.Context, writer io.Writer, data *RetweetedByData) (err error)
26         RenderSearchPage(ctx context.Context, writer io.Writer, data *SearchData) (err error)
27         RenderSettingsPage(ctx context.Context, writer io.Writer, data *SettingsData) (err error)
28 }
29
30 type renderer struct {
31         template *template.Template
32 }
33
34 func NewRenderer(templateGlobPattern string) (r *renderer, err error) {
35         t := template.New("default")
36         t, err = t.Funcs(template.FuncMap{
37                 "EmojiFilter":             EmojiFilter,
38                 "StatusContentFilter":     StatusContentFilter,
39                 "DisplayInteractionCount": DisplayInteractionCount,
40                 "TimeSince":               TimeSince,
41                 "FormatTimeRFC3339":       FormatTimeRFC3339,
42         }).ParseGlob(templateGlobPattern)
43         if err != nil {
44                 return
45         }
46         return &renderer{
47                 template: t,
48         }, nil
49 }
50
51 func (r *renderer) RenderErrorPage(ctx context.Context, writer io.Writer, errorData *ErrorData) {
52         r.template.ExecuteTemplate(writer, "error.tmpl", errorData)
53         return
54 }
55
56 func (r *renderer) RenderHomePage(ctx context.Context, writer io.Writer, homePageData *HomePageData) (err error) {
57         return r.template.ExecuteTemplate(writer, "homepage.tmpl", homePageData)
58 }
59
60 func (r *renderer) RenderSigninPage(ctx context.Context, writer io.Writer, signinData *SigninData) (err error) {
61         return r.template.ExecuteTemplate(writer, "signin.tmpl", signinData)
62 }
63
64 func (r *renderer) RenderTimelinePage(ctx context.Context, writer io.Writer, data *TimelineData) (err error) {
65         return r.template.ExecuteTemplate(writer, "timeline.tmpl", data)
66 }
67
68 func (r *renderer) RenderThreadPage(ctx context.Context, writer io.Writer, data *ThreadData) (err error) {
69         return r.template.ExecuteTemplate(writer, "thread.tmpl", data)
70 }
71
72 func (r *renderer) RenderNotificationPage(ctx context.Context, writer io.Writer, data *NotificationData) (err error) {
73         return r.template.ExecuteTemplate(writer, "notification.tmpl", data)
74 }
75
76 func (r *renderer) RenderUserPage(ctx context.Context, writer io.Writer, data *UserData) (err error) {
77         return r.template.ExecuteTemplate(writer, "user.tmpl", data)
78 }
79
80 func (r *renderer) RenderAboutPage(ctx context.Context, writer io.Writer, data *AboutData) (err error) {
81         return r.template.ExecuteTemplate(writer, "about.tmpl", data)
82 }
83
84 func (r *renderer) RenderEmojiPage(ctx context.Context, writer io.Writer, data *EmojiData) (err error) {
85         return r.template.ExecuteTemplate(writer, "emoji.tmpl", data)
86 }
87
88 func (r *renderer) RenderLikedByPage(ctx context.Context, writer io.Writer, data *LikedByData) (err error) {
89         return r.template.ExecuteTemplate(writer, "likedby.tmpl", data)
90 }
91
92 func (r *renderer) RenderRetweetedByPage(ctx context.Context, writer io.Writer, data *RetweetedByData) (err error) {
93         return r.template.ExecuteTemplate(writer, "retweetedby.tmpl", data)
94 }
95
96 func (r *renderer) RenderSearchPage(ctx context.Context, writer io.Writer, data *SearchData) (err error) {
97         return r.template.ExecuteTemplate(writer, "search.tmpl", data)
98 }
99
100 func (r *renderer) RenderSettingsPage(ctx context.Context, writer io.Writer, data *SettingsData) (err error) {
101         return r.template.ExecuteTemplate(writer, "settings.tmpl", data)
102 }
103
104 func EmojiFilter(content string, emojis []mastodon.Emoji) string {
105         var replacements []string
106         for _, e := range emojis {
107                 replacements = append(replacements, ":"+e.ShortCode+":", "<img class=\"status-emoji\" src=\""+e.URL+"\" alt=\""+e.ShortCode+"\" title=\""+e.ShortCode+"\" />")
108         }
109         return strings.NewReplacer(replacements...).Replace(content)
110 }
111
112 func StatusContentFilter(spoiler string, content string, emojis []mastodon.Emoji, mentions []mastodon.Mention) string {
113         if len(spoiler) > 0 {
114                 content = spoiler + "<br />" + content
115         }
116         var replacements []string
117         for _, e := range emojis {
118                 replacements = append(replacements, ":"+e.ShortCode+":", "<img class=\"status-emoji\" src=\""+e.URL+"\" alt=\""+e.ShortCode+"\" title=\""+e.ShortCode+"\" />")
119         }
120         for _, m := range mentions {
121                 replacements = append(replacements, "\""+m.URL+"\"", "\"/user/"+m.ID+"\"")
122         }
123         return strings.NewReplacer(replacements...).Replace(content)
124 }
125
126 func DisplayInteractionCount(c int64) string {
127         if c > 0 {
128                 return strconv.Itoa(int(c))
129         }
130         return ""
131 }
132
133 func TimeSince(t time.Time) string {
134         dur := time.Since(t)
135
136         s := dur.Seconds()
137         if s < 60 {
138                 return strconv.Itoa(int(s)) + "s"
139         }
140
141         m := dur.Minutes()
142         if m < 60 {
143                 return strconv.Itoa(int(m)) + "m"
144         }
145
146         h := dur.Hours()
147         if h < 24 {
148                 return strconv.Itoa(int(h)) + "h"
149         }
150
151         d := h / 24
152         if d < 30 {
153                 return strconv.Itoa(int(d)) + "d"
154         }
155
156         mo := d / 30
157         if mo < 12 {
158                 return strconv.Itoa(int(mo)) + "mo"
159         }
160
161         y := mo / 12
162         return strconv.Itoa(int(y)) + "y"
163 }
164
165 func FormatTimeRFC3339(t time.Time) string {
166         return t.Format(time.RFC3339)
167 }