0eb229c19e0c88545d3892283f7ed8a83664e8b7
[bloat] / renderer / renderer.go
1 package renderer
2
3 import (
4         "fmt"
5         "io"
6         "strconv"
7         "strings"
8         "text/template"
9         "time"
10
11         "bloat/mastodon"
12 )
13
14 type TemplateData struct {
15         Data interface{}
16         Ctx  *Context
17 }
18
19 type Renderer interface {
20         RenderSigninPage(ctx *Context, writer io.Writer, data *SigninData) (err error)
21         RenderErrorPage(ctx *Context, writer io.Writer, data *ErrorData)
22         RenderTimelinePage(ctx *Context, writer io.Writer, data *TimelineData) (err error)
23         RenderThreadPage(ctx *Context, writer io.Writer, data *ThreadData) (err error)
24         RenderNotificationPage(ctx *Context, writer io.Writer, data *NotificationData) (err error)
25         RenderUserPage(ctx *Context, writer io.Writer, data *UserData) (err error)
26         RenderUserSearchPage(ctx *Context, writer io.Writer, data *UserSearchData) (err error)
27         RenderAboutPage(ctx *Context, writer io.Writer, data *AboutData) (err error)
28         RenderEmojiPage(ctx *Context, writer io.Writer, data *EmojiData) (err error)
29         RenderLikedByPage(ctx *Context, writer io.Writer, data *LikedByData) (err error)
30         RenderRetweetedByPage(ctx *Context, writer io.Writer, data *RetweetedByData) (err error)
31         RenderSearchPage(ctx *Context, writer io.Writer, data *SearchData) (err error)
32         RenderSettingsPage(ctx *Context, writer io.Writer, data *SettingsData) (err error)
33 }
34
35 type renderer struct {
36         template *template.Template
37 }
38
39 func NewRenderer(templateGlobPattern string) (r *renderer, err error) {
40         t := template.New("default")
41         t, err = t.Funcs(template.FuncMap{
42                 "EmojiFilter":             EmojiFilter,
43                 "StatusContentFilter":     StatusContentFilter,
44                 "DisplayInteractionCount": DisplayInteractionCount,
45                 "TimeSince":               TimeSince,
46                 "FormatTimeRFC3339":       FormatTimeRFC3339,
47                 "FormatTimeRFC822":        FormatTimeRFC822,
48                 "WithContext":             WithContext,
49         }).ParseGlob(templateGlobPattern)
50         if err != nil {
51                 return
52         }
53         return &renderer{
54                 template: t,
55         }, nil
56 }
57
58 func (r *renderer) RenderSigninPage(ctx *Context, writer io.Writer,
59         signinData *SigninData) (err error) {
60         return r.template.ExecuteTemplate(writer, "signin.tmpl", WithContext(signinData, ctx))
61 }
62
63 func (r *renderer) RenderErrorPage(ctx *Context, writer io.Writer,
64         errorData *ErrorData) {
65         r.template.ExecuteTemplate(writer, "error.tmpl", WithContext(errorData, ctx))
66         return
67 }
68
69 func (r *renderer) RenderTimelinePage(ctx *Context, writer io.Writer,
70         data *TimelineData) (err error) {
71         return r.template.ExecuteTemplate(writer, "timeline.tmpl", WithContext(data, ctx))
72 }
73
74 func (r *renderer) RenderThreadPage(ctx *Context, writer io.Writer,
75         data *ThreadData) (err error) {
76         return r.template.ExecuteTemplate(writer, "thread.tmpl", WithContext(data, ctx))
77 }
78
79 func (r *renderer) RenderNotificationPage(ctx *Context, writer io.Writer,
80         data *NotificationData) (err error) {
81         return r.template.ExecuteTemplate(writer, "notification.tmpl", WithContext(data, ctx))
82 }
83
84 func (r *renderer) RenderUserPage(ctx *Context, writer io.Writer,
85         data *UserData) (err error) {
86         return r.template.ExecuteTemplate(writer, "user.tmpl", WithContext(data, ctx))
87 }
88
89 func (r *renderer) RenderUserSearchPage(ctx *Context, writer io.Writer, 
90         data *UserSearchData) (err error) {
91         return r.template.ExecuteTemplate(writer, "usersearch.tmpl", WithContext(data, ctx))
92 }
93
94 func (r *renderer) RenderAboutPage(ctx *Context, writer io.Writer,
95         data *AboutData) (err error) {
96         return r.template.ExecuteTemplate(writer, "about.tmpl", WithContext(data, ctx))
97 }
98
99 func (r *renderer) RenderEmojiPage(ctx *Context, writer io.Writer,
100         data *EmojiData) (err error) {
101         return r.template.ExecuteTemplate(writer, "emoji.tmpl", WithContext(data, ctx))
102 }
103
104 func (r *renderer) RenderLikedByPage(ctx *Context, writer io.Writer,
105         data *LikedByData) (err error) {
106         return r.template.ExecuteTemplate(writer, "likedby.tmpl", WithContext(data, ctx))
107 }
108
109 func (r *renderer) RenderRetweetedByPage(ctx *Context, writer io.Writer,
110         data *RetweetedByData) (err error) {
111         return r.template.ExecuteTemplate(writer, "retweetedby.tmpl", WithContext(data, ctx))
112 }
113
114 func (r *renderer) RenderSearchPage(ctx *Context, writer io.Writer,
115         data *SearchData) (err error) {
116         return r.template.ExecuteTemplate(writer, "search.tmpl", WithContext(data, ctx))
117 }
118
119 func (r *renderer) RenderSettingsPage(ctx *Context, writer io.Writer,
120         data *SettingsData) (err error) {
121         return r.template.ExecuteTemplate(writer, "settings.tmpl", WithContext(data, ctx))
122 }
123
124 func EmojiFilter(content string, emojis []mastodon.Emoji) string {
125         var replacements []string
126         var r string
127         for _, e := range emojis {
128                 r = fmt.Sprintf("<img class=\"status-emoji\" src=\"%s\" alt=\"%s\" title=\"%s\" />",
129                         e.URL, e.ShortCode, e.ShortCode)
130                 replacements = append(replacements, ":"+e.ShortCode+":", r)
131         }
132         return strings.NewReplacer(replacements...).Replace(content)
133 }
134
135 func StatusContentFilter(spoiler string, content string,
136         emojis []mastodon.Emoji, mentions []mastodon.Mention) string {
137
138         var replacements []string
139         var r string
140         if len(spoiler) > 0 {
141                 content = spoiler + "<br />" + content
142         }
143         for _, e := range emojis {
144                 r = fmt.Sprintf("<img class=\"status-emoji\" src=\"%s\" alt=\"%s\" title=\"%s\" />",
145                         e.URL, e.ShortCode, e.ShortCode)
146                 replacements = append(replacements, ":"+e.ShortCode+":", r)
147         }
148         for _, m := range mentions {
149                 replacements = append(replacements, "\""+m.URL+"\"", "\"/user/"+m.ID+"\"")
150         }
151         return strings.NewReplacer(replacements...).Replace(content)
152 }
153
154 func DisplayInteractionCount(c int64) string {
155         if c > 0 {
156                 return strconv.Itoa(int(c))
157         }
158         return ""
159 }
160
161 func TimeSince(t time.Time) string {
162         dur := time.Since(t)
163         s := dur.Seconds()
164         if s < 60 {
165                 return strconv.Itoa(int(s)) + "s"
166         }
167         m := dur.Minutes()
168         if m < 60 {
169                 return strconv.Itoa(int(m)) + "m"
170         }
171         h := dur.Hours()
172         if h < 24 {
173                 return strconv.Itoa(int(h)) + "h"
174         }
175         d := h / 24
176         if d < 30 {
177                 return strconv.Itoa(int(d)) + "d"
178         }
179         mo := d / 30
180         if mo < 12 {
181                 return strconv.Itoa(int(mo)) + "mo"
182         }
183         y := mo / 12
184         return strconv.Itoa(int(y)) + "y"
185 }
186
187 func FormatTimeRFC3339(t time.Time) string {
188         return t.Format(time.RFC3339)
189 }
190
191 func FormatTimeRFC822(t time.Time) string {
192         return t.Format(time.RFC822)
193 }
194
195 func WithContext(data interface{}, ctx *Context) TemplateData {
196         return TemplateData{data, ctx}
197 }