Imporve emoji style
[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 Page string
15
16 const (
17         SigninPage       = "signin.tmpl"
18         ErrorPage        = "error.tmpl"
19         NavPage          = "nav.tmpl"
20         RootPage         = "root.tmpl"
21         TimelinePage     = "timeline.tmpl"
22         ThreadPage       = "thread.tmpl"
23         NotificationPage = "notification.tmpl"
24         UserPage         = "user.tmpl"
25         UserSearchPage   = "usersearch.tmpl"
26         AboutPage        = "about.tmpl"
27         EmojiPage        = "emoji.tmpl"
28         LikedByPage      = "likedby.tmpl"
29         RetweetedByPage  = "retweetedby.tmpl"
30         SearchPage       = "search.tmpl"
31         SettingsPage     = "settings.tmpl"
32 )
33
34 type TemplateData struct {
35         Data interface{}
36         Ctx  *Context
37 }
38
39 func emojiFilter(content string, emojis []mastodon.Emoji) string {
40         var replacements []string
41         var r string
42         for _, e := range emojis {
43                 r = fmt.Sprintf("<img class=\"emoji\" src=\"%s\" alt=\":%s:\" title=\":%s:\" />",
44                         e.URL, e.ShortCode, e.ShortCode)
45                 replacements = append(replacements, ":"+e.ShortCode+":", r)
46         }
47         return strings.NewReplacer(replacements...).Replace(content)
48 }
49
50 func statusContentFilter(spoiler string, content string,
51         emojis []mastodon.Emoji, mentions []mastodon.Mention) string {
52
53         var replacements []string
54         var r string
55         if len(spoiler) > 0 {
56                 content = spoiler + "<br />" + content
57         }
58         for _, e := range emojis {
59                 r = fmt.Sprintf("<img class=\"emoji\" src=\"%s\" alt=\":%s:\" title=\":%s:\" />",
60                         e.URL, e.ShortCode, e.ShortCode)
61                 replacements = append(replacements, ":"+e.ShortCode+":", r)
62         }
63         for _, m := range mentions {
64                 replacements = append(replacements, "\""+m.URL+"\"", "\"/user/"+m.ID+"\"")
65         }
66         return strings.NewReplacer(replacements...).Replace(content)
67 }
68
69 func displayInteractionCount(c int64) string {
70         if c > 0 {
71                 return strconv.Itoa(int(c))
72         }
73         return ""
74 }
75
76 func DurToStr(dur time.Duration) string {
77         s := dur.Seconds()
78         if s < 60 {
79                 return strconv.Itoa(int(s)) + "s"
80         }
81         m := dur.Minutes()
82         if m < 60 {
83                 return strconv.Itoa(int(m)) + "m"
84         }
85         h := dur.Hours()
86         if h < 24 {
87                 return strconv.Itoa(int(h)) + "h"
88         }
89         d := h / 24
90         if d < 30 {
91                 return strconv.Itoa(int(d)) + "d"
92         }
93         mo := d / 30
94         if mo < 12 {
95                 return strconv.Itoa(int(mo)) + "mo"
96         }
97         y := mo / 12
98         return strconv.Itoa(int(y)) + "y"
99 }
100
101 func timeSince(t time.Time) string {
102         d := time.Since(t)
103         if d < 0 {
104                 d = 0
105         }
106         return DurToStr(d)
107 }
108
109 func timeUntil(t time.Time) string {
110         d := time.Until(t)
111         if d < 0 {
112                 d = 0
113         }
114         return DurToStr(d)
115 }
116
117 func formatTimeRFC3339(t time.Time) string {
118         return t.Format(time.RFC3339)
119 }
120
121 func formatTimeRFC822(t time.Time) string {
122         return t.Format(time.RFC822)
123 }
124
125 func withContext(data interface{}, ctx *Context) TemplateData {
126         return TemplateData{data, ctx}
127 }
128
129 type Renderer interface {
130         Render(ctx *Context, writer io.Writer, page string, data interface{}) (err error)
131 }
132
133 type renderer struct {
134         template *template.Template
135 }
136
137 func NewRenderer(templateGlobPattern string) (r *renderer, err error) {
138         t := template.New("default")
139         t, err = t.Funcs(template.FuncMap{
140                 "EmojiFilter":             emojiFilter,
141                 "StatusContentFilter":     statusContentFilter,
142                 "DisplayInteractionCount": displayInteractionCount,
143                 "TimeSince":               timeSince,
144                 "TimeUntil":               timeUntil,
145                 "FormatTimeRFC3339":       formatTimeRFC3339,
146                 "FormatTimeRFC822":        formatTimeRFC822,
147                 "WithContext":             withContext,
148         }).ParseGlob(templateGlobPattern)
149         if err != nil {
150                 return
151         }
152         return &renderer{
153                 template: t,
154         }, nil
155 }
156
157 func (r *renderer) Render(ctx *Context, writer io.Writer,
158         page string, data interface{}) (err error) {
159         return r.template.ExecuteTemplate(writer, page, withContext(data, ctx))
160 }