2019-12-13 18:08:26 +00:00
|
|
|
package renderer
|
|
|
|
|
|
|
|
import (
|
2020-01-28 17:51:00 +00:00
|
|
|
"fmt"
|
2019-12-13 18:08:26 +00:00
|
|
|
"io"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"text/template"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"mastodon"
|
|
|
|
)
|
|
|
|
|
2020-01-12 17:16:57 +00:00
|
|
|
var (
|
|
|
|
icons = map[string]string{
|
|
|
|
"envelope": "/static/icons/envelope.png",
|
|
|
|
"dark-envelope": "/static/icons/dark-envelope.png",
|
|
|
|
"globe": "/static/icons/globe.png",
|
|
|
|
"dark-globe": "/static/icons/dark-globe.png",
|
|
|
|
"liked": "/static/icons/liked.png",
|
|
|
|
"dark-liked": "/static/icons/liked.png",
|
|
|
|
"link": "/static/icons/link.png",
|
|
|
|
"dark-link": "/static/icons/dark-link.png",
|
|
|
|
"lock": "/static/icons/lock.png",
|
|
|
|
"dark-lock": "/static/icons/dark-lock.png",
|
|
|
|
"mail-forward": "/static/icons/mail-forward.png",
|
|
|
|
"dark-mail-forward": "/static/icons/dark-mail-forward.png",
|
|
|
|
"reply": "/static/icons/reply.png",
|
|
|
|
"dark-reply": "/static/icons/dark-reply.png",
|
|
|
|
"retweet": "/static/icons/retweet.png",
|
|
|
|
"dark-retweet": "/static/icons/dark-retweet.png",
|
|
|
|
"retweeted": "/static/icons/retweeted.png",
|
|
|
|
"dark-retweeted": "/static/icons/retweeted.png",
|
|
|
|
"smile-o": "/static/icons/smile-o.png",
|
|
|
|
"dark-smile-o": "/static/icons/dark-smile-o.png",
|
|
|
|
"star-o": "/static/icons/star-o.png",
|
|
|
|
"dark-star-o": "/static/icons/dark-star-o.png",
|
|
|
|
"star": "/static/icons/star.png",
|
|
|
|
"dark-star": "/static/icons/dark-star.png",
|
|
|
|
"unlock-alt": "/static/icons/unlock-alt.png",
|
|
|
|
"dark-unlock-alt": "/static/icons/dark-unlock-alt.png",
|
|
|
|
"user-plus": "/static/icons/user-plus.png",
|
|
|
|
"dark-user-plus": "/static/icons/dark-user-plus.png",
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2020-01-14 16:57:16 +00:00
|
|
|
type TemplateData struct {
|
|
|
|
Data interface{}
|
|
|
|
Ctx *Context
|
|
|
|
}
|
|
|
|
|
2019-12-13 18:08:26 +00:00
|
|
|
type Renderer interface {
|
2020-01-14 16:57:16 +00:00
|
|
|
RenderSigninPage(ctx *Context, writer io.Writer, data *SigninData) (err error)
|
|
|
|
RenderErrorPage(ctx *Context, writer io.Writer, data *ErrorData)
|
|
|
|
RenderTimelinePage(ctx *Context, writer io.Writer, data *TimelineData) (err error)
|
|
|
|
RenderThreadPage(ctx *Context, writer io.Writer, data *ThreadData) (err error)
|
|
|
|
RenderNotificationPage(ctx *Context, writer io.Writer, data *NotificationData) (err error)
|
|
|
|
RenderUserPage(ctx *Context, writer io.Writer, data *UserData) (err error)
|
|
|
|
RenderAboutPage(ctx *Context, writer io.Writer, data *AboutData) (err error)
|
|
|
|
RenderEmojiPage(ctx *Context, writer io.Writer, data *EmojiData) (err error)
|
|
|
|
RenderLikedByPage(ctx *Context, writer io.Writer, data *LikedByData) (err error)
|
|
|
|
RenderRetweetedByPage(ctx *Context, writer io.Writer, data *RetweetedByData) (err error)
|
|
|
|
RenderFollowingPage(ctx *Context, writer io.Writer, data *FollowingData) (err error)
|
|
|
|
RenderFollowersPage(ctx *Context, writer io.Writer, data *FollowersData) (err error)
|
|
|
|
RenderSearchPage(ctx *Context, writer io.Writer, data *SearchData) (err error)
|
|
|
|
RenderSettingsPage(ctx *Context, writer io.Writer, data *SettingsData) (err error)
|
2019-12-13 18:08:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type renderer struct {
|
|
|
|
template *template.Template
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewRenderer(templateGlobPattern string) (r *renderer, err error) {
|
|
|
|
t := template.New("default")
|
|
|
|
t, err = t.Funcs(template.FuncMap{
|
2019-12-21 05:48:06 +00:00
|
|
|
"EmojiFilter": EmojiFilter,
|
|
|
|
"StatusContentFilter": StatusContentFilter,
|
2019-12-13 18:08:26 +00:00
|
|
|
"DisplayInteractionCount": DisplayInteractionCount,
|
|
|
|
"TimeSince": TimeSince,
|
|
|
|
"FormatTimeRFC3339": FormatTimeRFC3339,
|
2020-01-10 04:05:01 +00:00
|
|
|
"FormatTimeRFC822": FormatTimeRFC822,
|
2020-01-12 17:16:57 +00:00
|
|
|
"GetIcon": GetIcon,
|
2020-01-14 16:57:16 +00:00
|
|
|
"WithContext": WithContext,
|
2019-12-13 18:08:26 +00:00
|
|
|
}).ParseGlob(templateGlobPattern)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return &renderer{
|
|
|
|
template: t,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2020-01-28 17:51:00 +00:00
|
|
|
func (r *renderer) RenderSigninPage(ctx *Context, writer io.Writer,
|
|
|
|
signinData *SigninData) (err error) {
|
2020-01-14 16:57:16 +00:00
|
|
|
return r.template.ExecuteTemplate(writer, "signin.tmpl", WithContext(signinData, ctx))
|
2019-12-13 18:08:26 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 17:51:00 +00:00
|
|
|
func (r *renderer) RenderErrorPage(ctx *Context, writer io.Writer,
|
|
|
|
errorData *ErrorData) {
|
2020-01-14 16:57:16 +00:00
|
|
|
r.template.ExecuteTemplate(writer, "error.tmpl", WithContext(errorData, ctx))
|
|
|
|
return
|
2019-12-13 18:08:26 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 17:51:00 +00:00
|
|
|
func (r *renderer) RenderTimelinePage(ctx *Context, writer io.Writer,
|
|
|
|
data *TimelineData) (err error) {
|
2020-01-14 16:57:16 +00:00
|
|
|
return r.template.ExecuteTemplate(writer, "timeline.tmpl", WithContext(data, ctx))
|
2019-12-13 18:08:26 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 17:51:00 +00:00
|
|
|
func (r *renderer) RenderThreadPage(ctx *Context, writer io.Writer,
|
|
|
|
data *ThreadData) (err error) {
|
2020-01-14 16:57:16 +00:00
|
|
|
return r.template.ExecuteTemplate(writer, "thread.tmpl", WithContext(data, ctx))
|
2019-12-13 18:08:26 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 17:51:00 +00:00
|
|
|
func (r *renderer) RenderNotificationPage(ctx *Context, writer io.Writer,
|
|
|
|
data *NotificationData) (err error) {
|
2020-01-14 16:57:16 +00:00
|
|
|
return r.template.ExecuteTemplate(writer, "notification.tmpl", WithContext(data, ctx))
|
2019-12-15 17:37:58 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 17:51:00 +00:00
|
|
|
func (r *renderer) RenderUserPage(ctx *Context, writer io.Writer,
|
|
|
|
data *UserData) (err error) {
|
2020-01-14 16:57:16 +00:00
|
|
|
return r.template.ExecuteTemplate(writer, "user.tmpl", WithContext(data, ctx))
|
2019-12-20 18:30:20 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 17:51:00 +00:00
|
|
|
func (r *renderer) RenderAboutPage(ctx *Context, writer io.Writer,
|
|
|
|
data *AboutData) (err error) {
|
2020-01-14 16:57:16 +00:00
|
|
|
return r.template.ExecuteTemplate(writer, "about.tmpl", WithContext(data, ctx))
|
2019-12-21 05:48:48 +00:00
|
|
|
}
|
2019-12-21 05:48:06 +00:00
|
|
|
|
2020-01-28 17:51:00 +00:00
|
|
|
func (r *renderer) RenderEmojiPage(ctx *Context, writer io.Writer,
|
|
|
|
data *EmojiData) (err error) {
|
2020-01-14 16:57:16 +00:00
|
|
|
return r.template.ExecuteTemplate(writer, "emoji.tmpl", WithContext(data, ctx))
|
2019-12-22 18:10:42 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 17:51:00 +00:00
|
|
|
func (r *renderer) RenderLikedByPage(ctx *Context, writer io.Writer,
|
|
|
|
data *LikedByData) (err error) {
|
2020-01-14 16:57:16 +00:00
|
|
|
return r.template.ExecuteTemplate(writer, "likedby.tmpl", WithContext(data, ctx))
|
2019-12-26 09:11:24 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 17:51:00 +00:00
|
|
|
func (r *renderer) RenderRetweetedByPage(ctx *Context, writer io.Writer,
|
|
|
|
data *RetweetedByData) (err error) {
|
2020-01-14 16:57:16 +00:00
|
|
|
return r.template.ExecuteTemplate(writer, "retweetedby.tmpl", WithContext(data, ctx))
|
2019-12-26 09:11:24 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 17:51:00 +00:00
|
|
|
func (r *renderer) RenderFollowingPage(ctx *Context, writer io.Writer,
|
|
|
|
data *FollowingData) (err error) {
|
2020-01-14 16:57:16 +00:00
|
|
|
return r.template.ExecuteTemplate(writer, "following.tmpl", WithContext(data, ctx))
|
2019-12-29 03:43:57 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 17:51:00 +00:00
|
|
|
func (r *renderer) RenderFollowersPage(ctx *Context, writer io.Writer,
|
|
|
|
data *FollowersData) (err error) {
|
2020-01-14 16:57:16 +00:00
|
|
|
return r.template.ExecuteTemplate(writer, "followers.tmpl", WithContext(data, ctx))
|
2019-12-29 03:43:57 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 17:51:00 +00:00
|
|
|
func (r *renderer) RenderSearchPage(ctx *Context, writer io.Writer,
|
|
|
|
data *SearchData) (err error) {
|
2020-01-14 16:57:16 +00:00
|
|
|
return r.template.ExecuteTemplate(writer, "search.tmpl", WithContext(data, ctx))
|
2019-12-26 19:18:09 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 17:51:00 +00:00
|
|
|
func (r *renderer) RenderSettingsPage(ctx *Context, writer io.Writer,
|
|
|
|
data *SettingsData) (err error) {
|
2020-01-14 16:57:16 +00:00
|
|
|
return r.template.ExecuteTemplate(writer, "settings.tmpl", WithContext(data, ctx))
|
2019-12-27 08:06:43 +00:00
|
|
|
}
|
|
|
|
|
2019-12-21 05:48:06 +00:00
|
|
|
func EmojiFilter(content string, emojis []mastodon.Emoji) string {
|
|
|
|
var replacements []string
|
2020-01-28 17:51:00 +00:00
|
|
|
var r string
|
2019-12-21 05:48:06 +00:00
|
|
|
for _, e := range emojis {
|
2020-01-28 17:51:00 +00:00
|
|
|
r = fmt.Sprintf("<img class=\"status-emoji\" src=\"%s\" alt=\"%s\" title=\"%s\" />",
|
|
|
|
e.URL, e.ShortCode, e.ShortCode)
|
|
|
|
replacements = append(replacements, ":"+e.ShortCode+":", r)
|
2019-12-21 05:48:06 +00:00
|
|
|
}
|
|
|
|
return strings.NewReplacer(replacements...).Replace(content)
|
|
|
|
}
|
|
|
|
|
2020-01-28 17:51:00 +00:00
|
|
|
func StatusContentFilter(spoiler string, content string,
|
|
|
|
emojis []mastodon.Emoji, mentions []mastodon.Mention) string {
|
|
|
|
|
|
|
|
var replacements []string
|
|
|
|
var r string
|
2019-12-21 07:22:21 +00:00
|
|
|
if len(spoiler) > 0 {
|
|
|
|
content = spoiler + "<br />" + content
|
|
|
|
}
|
2019-12-13 18:08:26 +00:00
|
|
|
for _, e := range emojis {
|
2020-01-28 17:51:00 +00:00
|
|
|
r = fmt.Sprintf("<img class=\"status-emoji\" src=\"%s\" alt=\"%s\" title=\"%s\" />",
|
|
|
|
e.URL, e.ShortCode, e.ShortCode)
|
|
|
|
replacements = append(replacements, ":"+e.ShortCode+":", r)
|
2019-12-21 05:48:06 +00:00
|
|
|
}
|
|
|
|
for _, m := range mentions {
|
|
|
|
replacements = append(replacements, "\""+m.URL+"\"", "\"/user/"+m.ID+"\"")
|
2019-12-13 18:08:26 +00:00
|
|
|
}
|
2019-12-21 05:48:06 +00:00
|
|
|
return strings.NewReplacer(replacements...).Replace(content)
|
2019-12-13 18:08:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func DisplayInteractionCount(c int64) string {
|
|
|
|
if c > 0 {
|
|
|
|
return strconv.Itoa(int(c))
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func TimeSince(t time.Time) string {
|
|
|
|
dur := time.Since(t)
|
|
|
|
s := dur.Seconds()
|
|
|
|
if s < 60 {
|
|
|
|
return strconv.Itoa(int(s)) + "s"
|
|
|
|
}
|
|
|
|
m := dur.Minutes()
|
|
|
|
if m < 60 {
|
|
|
|
return strconv.Itoa(int(m)) + "m"
|
|
|
|
}
|
|
|
|
h := dur.Hours()
|
|
|
|
if h < 24 {
|
|
|
|
return strconv.Itoa(int(h)) + "h"
|
|
|
|
}
|
|
|
|
d := h / 24
|
|
|
|
if d < 30 {
|
|
|
|
return strconv.Itoa(int(d)) + "d"
|
|
|
|
}
|
|
|
|
mo := d / 30
|
|
|
|
if mo < 12 {
|
|
|
|
return strconv.Itoa(int(mo)) + "mo"
|
|
|
|
}
|
2019-12-20 18:31:55 +00:00
|
|
|
y := mo / 12
|
2019-12-13 18:08:26 +00:00
|
|
|
return strconv.Itoa(int(y)) + "y"
|
|
|
|
}
|
|
|
|
|
|
|
|
func FormatTimeRFC3339(t time.Time) string {
|
|
|
|
return t.Format(time.RFC3339)
|
|
|
|
}
|
2020-01-10 04:05:01 +00:00
|
|
|
|
|
|
|
func FormatTimeRFC822(t time.Time) string {
|
|
|
|
return t.Format(time.RFC822)
|
|
|
|
}
|
2020-01-12 17:16:57 +00:00
|
|
|
|
|
|
|
func GetIcon(name string, darkMode bool) (icon string) {
|
|
|
|
if darkMode {
|
|
|
|
name = "dark-" + name
|
|
|
|
}
|
|
|
|
icon, _ = icons[name]
|
|
|
|
return
|
|
|
|
}
|
2020-01-14 16:57:16 +00:00
|
|
|
|
|
|
|
func WithContext(data interface{}, ctx *Context) TemplateData {
|
|
|
|
return TemplateData{data, ctx}
|
|
|
|
}
|